Compare commits

...

55 Commits

Author SHA1 Message Date
benedetta davico aa833310da Update validator-api-tests.yml 2023-02-22 13:05:25 +01:00
benedettadavico 17ed80018f adding tests for circulating endpoints 2023-02-06 11:22:52 +01:00
benedettadavico cdfcb065b7 merge develop into feature/wallet-tests 2023-02-06 09:41:23 +01:00
benedettadavico 529db99b8e Merge branch 'develop' into feature/wallet-tests 2023-01-26 11:59:07 +01:00
benedettadavico fd4f083742 adding missing endpoints 2023-01-26 11:58:41 +01:00
benedettadavico 06256e512d adding tests 2023-01-24 15:18:30 +01:00
benedettadavico 6b4e915cd6 Merge branch 'release/v1.1.7' into feature/wallet-tests 2023-01-24 13:20:36 +01:00
benedettadavico 19cdb7f629 adding tests, cleaning up 2023-01-18 10:52:57 +01:00
benedettadavico 029f8904fe Merge branch 'develop' of https://github.com/nymtech/nym into feature/wallet-tests 2023-01-17 16:12:58 +01:00
benedettadavico 5f852e04a0 run diff envs 2023-01-06 17:49:23 +01:00
benedettadavico 365b955c90 Merge branch 'release/v1.1.5' into feature/wallet-tests 2023-01-06 17:05:22 +01:00
benedettadavico f14a87809e editing tests 2023-01-06 17:04:22 +01:00
benedettadavico a18eec2fe5 fix renaming merge conflicts 2023-01-04 10:50:00 +01:00
benedettadavico 3c99644420 conflicts 2023-01-04 10:45:57 +01:00
benedettadavico d6e6369389 Merge branch 'release/v1.1.4' of https://github.com/nymtech/nym into feature/wallet-tests 2022-12-21 17:13:01 +01:00
Fouad eff1f383c3 Feature/node settings apy playground (#1677) (#2738)
* initial ui for test my node

use svg for node path

add stories for test my node

* add initial rewards calculation

* update validation for rewards playground

* init playground with default values

* get node uptime

* get mixnode reward estimation

* calculate saturation

calculate stake saturation

* Make ComputeRewardEstParam derive Debug

* set active set to be always true

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2022-12-21 12:56:40 +00:00
benedettadavico a192e0d745 Add family to mixnode detailed 2022-12-08 17:52:20 +01:00
benedettadavico 6168ac9e9b Merge branch 'feature/wallet-tests' of https://github.com/nymtech/nym into feature/wallet-tests 2022-12-08 17:39:54 +01:00
benedettadavico f7d96042de Merge branch 'develop' of https://github.com/nymtech/nym into feature/wallet-tests 2022-12-08 17:38:47 +01:00
tommy 353ccbe258 refine... 2022-11-16 17:07:06 +01:00
tommy 6b74a1f091 add ignore - for some reason it wasn't there 2022-11-16 15:48:29 +01:00
tommy 2f191f50ee remove 2022-11-16 13:24:28 +01:00
tommy 0de1196f85 put in placeholder for things 2022-11-16 13:24:05 +01:00
tommy 78c1cc05e1 remove the check for the wallet 2022-11-16 12:42:08 +01:00
tommy ee8101db04 run formatter and remove duplicate deleteFile 2022-11-16 12:36:09 +01:00
benedettadavico cd6c68907c WIP 2022-11-14 13:28:34 +01:00
benedettadavico 452e9ed637 WIP; debugging issues 2022-11-11 17:44:03 +01:00
benedettadavico eeeb8052b6 Merge branch 'release/v1.1.0' into feature/wallet-tests 2022-11-11 10:23:49 +01:00
benedettadavico bc79ee0cad WIP 2022-11-10 18:29:16 +01:00
benedettadavico aa5cbd06d4 WIP 2022-11-10 11:58:08 +01:00
benedettadavico 15cdf579b2 WIP 2022-11-10 11:57:13 +01:00
benedettadavico caf9429431 Merge branch 'release/v1.1.0', remote-tracking branch 'origin' into feature/wallet-tests 2022-11-10 10:30:06 +01:00
benedettadavico 708782fdf9 WIP 2022-11-10 10:27:11 +01:00
benedettadavico 3fc11d8bef WIP 2022-11-09 18:56:27 +01:00
benedettadavico 2eadda2295 WIP 2022-11-09 16:51:32 +01:00
benedettadavico 9434cb266b WIP 2022-11-09 14:28:49 +01:00
benedettadavico 8f49db1150 WIP 2022-11-09 10:57:24 +01:00
benedettadavico c45e8da43d Merge branch 'develop-with-release-1.1.0-merged-in' into feature/validator-api-tests 2022-11-09 10:15:54 +01:00
benedettadavico 12cc49a734 WIP 2022-11-09 10:05:47 +01:00
benedettadavico 7e56a9e88c WIP 2022-11-08 16:22:39 +01:00
benedettadavico 9790009eac WIP 2022-11-08 12:30:27 +01:00
benedettadavico 379d593daf Updating more tests 2022-11-07 18:37:31 +01:00
benedettadavico ce75b99b6f Merge branch 'release/v1.1.0' into feature/validator-api-tests 2022-11-07 17:36:21 +01:00
benedettadavico bcb7c41fd7 Updating validator api tests for v2 contracts 2022-11-07 17:31:25 +01:00
benedettadavico bb091ce47f Updating validator api tests for v2 contracts 2022-11-07 17:28:13 +01:00
benedettadavico effed4d7d6 Merge branch 'release/v1.1.0' into feature/validator-api-tests 2022-11-07 09:36:16 +01:00
benedettadavico d480ddb133 fixing failing tests 2022-08-15 15:20:23 +02:00
benedettadavico b119820591 Clean up 2022-08-15 09:25:28 +02:00
benedettadavico e128949dc2 Clean up 2022-08-13 20:40:08 +02:00
benedettadavico 9499b987e5 possible approach to validating address length and proxy type 2022-08-13 20:31:50 +02:00
benedettadavico d6ac786295 adding tests 2022-08-12 15:51:23 +02:00
tommy 4d09d9c3db remove 1-2-1 mapping 2022-08-12 13:30:27 +02:00
tommy 8c9044adf3 remove the need to map to type 2022-08-12 13:26:46 +02:00
tommy 472085ca52 Fix up look sharp
- added missing .git files
- fixed paths
- run the linter
2022-08-12 11:18:17 +02:00
benedettadavico 2f089e80ff adding onto the validator-api tests 2022-08-12 10:12:57 +02:00
85 changed files with 11042 additions and 1846 deletions
Vendored
BIN
View File
Binary file not shown.
+32
View File
@@ -0,0 +1,32 @@
name: Tests for validator API
on:
push:
paths:
- "nym-api/tests/**"
defaults:
run:
working-directory: nym-api/tests
jobs:
test:
name: validator api tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Node v18
uses: actions/setup-node@v3
with:
node-version: 18.1.0
- name: Install yarn
run: yarn install
- name: Run yarn
run: yarn
- name: Launch tests
run: yarn test:qa
working-directory: nym-api/tests
+783
View File
File diff suppressed because one or more lines are too long
@@ -12,12 +12,22 @@ describe("Get circulating supply", (): void => {
it("Get circulating supply amounts", async (): Promise<void> => {
const response = await contract.getCirculatingSupply();
let initial: number = +response.initial_supply.amount;
let mixmining: number = +response.mixmining_reserve.amount;
let vest: number = +response.vesting_tokens.amount;
let circsupply: number = +response.circulating_supply.amount;
const initial: number = +response.total_supply.amount;
const mixmining: number = +response.mixmining_reserve.amount;
const vest: number = +response.vesting_tokens.amount;
const circsupply: number = +response.circulating_supply.amount;
expect(typeof response.vesting_tokens.amount).toBe("string");
expect(initial - mixmining - vest).toStrictEqual(circsupply);
});
});
});
it("Get total supply value", async (): Promise<void> => {
const response = await contract.getTotalSupplyValue();
expect(typeof response).toBe("number");
});
it("Get circulating supply value", async (): Promise<void> => {
const response = await contract.getCirculatingSupplyValue();
expect(typeof response).toBe("number");
});
});
@@ -17,8 +17,12 @@ describe("Get mixnode data", (): void => {
//bond information overview
expect(typeof mixnode.bond_information.mix_id).toBe("number");
expect(typeof mixnode.bond_information.owner).toBe("string");
expect(typeof mixnode.bond_information.original_pledge.amount).toBe("string");
expect(typeof mixnode.bond_information.original_pledge.denom).toBe("string");
expect(typeof mixnode.bond_information.original_pledge.amount).toBe(
"string"
);
expect(typeof mixnode.bond_information.original_pledge.denom).toBe(
"string"
);
expect(typeof mixnode.bond_information.layer).toBe("number");
expect(typeof mixnode.bond_information.bonding_height).toBe("number");
expect(typeof mixnode.bond_information.is_unbonding).toBe("boolean");
@@ -31,8 +35,12 @@ describe("Get mixnode data", (): void => {
//mixnode
expect(typeof mixnode.bond_information.mix_node.host).toBe("string");
expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual(8000);
expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe("number");
expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual(
8000
);
expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe(
"number"
);
expect(typeof mixnode.bond_information.mix_node.mix_port).toBe("number");
expect(mixnode.bond_information.mix_node.mix_port).toStrictEqual(1789);
expect(mixnode.bond_information.mix_node.verloc_port).toStrictEqual(1790);
@@ -40,30 +48,39 @@ describe("Get mixnode data", (): void => {
const identitykey = mixnode.bond_information.mix_node.identity_key;
if (typeof identitykey === "string") {
if (identitykey.length === 43) {
return true
}
else expect(identitykey).toHaveLength(44);
return true;
} else expect(identitykey).toHaveLength(44);
}
const sphinx = mixnode.bond_information.mix_node.sphinx_key
const sphinx = mixnode.bond_information.mix_node.sphinx_key;
if (typeof sphinx === "string") {
if (sphinx.length === 43) {
return true
}
else expect(sphinx).toHaveLength(44);
return true;
} else expect(sphinx).toHaveLength(44);
}
//rewarding details
expect(typeof mixnode.rewarding_details.cost_params.profit_margin_percent).toBe("string");
expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string");
expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string");
expect(
typeof mixnode.rewarding_details.cost_params.profit_margin_percent
).toBe("string");
expect(
typeof mixnode.rewarding_details.cost_params.interval_operating_cost
.denom
).toBe("string");
expect(
typeof mixnode.rewarding_details.cost_params.interval_operating_cost
.amount
).toBe("string");
expect(typeof mixnode.rewarding_details.operator).toBe("string");
expect(typeof mixnode.rewarding_details.delegates).toBe("string");
expect(typeof mixnode.rewarding_details.total_unit_reward).toBe("string");
expect(typeof mixnode.rewarding_details.unit_delegation).toBe("string");
expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe("number");
expect(typeof mixnode.rewarding_details.unique_delegations).toBe("number");
expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe(
"number"
);
expect(typeof mixnode.rewarding_details.unique_delegations).toBe(
"number"
);
});
});
@@ -79,85 +96,139 @@ describe("Get mixnode data", (): void => {
expect(typeof mixnode.family).toBe("string");
//mixnode details bond info
expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe("number")
expect(typeof mixnode.mixnode_details.bond_information.owner).toBe("string");
expect(typeof mixnode.mixnode_details.bond_information.original_pledge.amount).toBe("string");
expect(typeof mixnode.mixnode_details.bond_information.original_pledge.denom).toBe("string");
expect(typeof mixnode.mixnode_details.bond_information.layer).toBe("number");
expect(typeof mixnode.mixnode_details.bond_information.bonding_height).toBe("number");
expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe("boolean");
expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe(
"number"
);
expect(typeof mixnode.mixnode_details.bond_information.owner).toBe(
"string"
);
expect(
typeof mixnode.mixnode_details.bond_information.original_pledge.amount
).toBe("string");
expect(
typeof mixnode.mixnode_details.bond_information.original_pledge.denom
).toBe("string");
expect(typeof mixnode.mixnode_details.bond_information.layer).toBe(
"number"
);
expect(
typeof mixnode.mixnode_details.bond_information.bonding_height
).toBe("number");
expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe(
"boolean"
);
if (mixnode.mixnode_details.bond_information.proxy === null) {
return true;
}
else {
expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe("string");
} else {
expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe(
"string"
);
}
//mixnode
expect(typeof mixnode.mixnode_details.bond_information.mix_node.host).toBe("string")
expect(mixnode.mixnode_details.bond_information.mix_node.http_api_port).toStrictEqual(8000);
expect(typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port).toBe("number")
expect(typeof mixnode.mixnode_details.bond_information.mix_node.mix_port).toBe("number")
expect(mixnode.mixnode_details.bond_information.mix_node.mix_port).toStrictEqual(1789);
expect(mixnode.mixnode_details.bond_information.mix_node.verloc_port).toStrictEqual(1790)
expect(
typeof mixnode.mixnode_details.bond_information.mix_node.host
).toBe("string");
expect(
mixnode.mixnode_details.bond_information.mix_node.http_api_port
).toStrictEqual(8000);
expect(
typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port
).toBe("number");
expect(
typeof mixnode.mixnode_details.bond_information.mix_node.mix_port
).toBe("number");
expect(
mixnode.mixnode_details.bond_information.mix_node.mix_port
).toStrictEqual(1789);
expect(
mixnode.mixnode_details.bond_information.mix_node.verloc_port
).toStrictEqual(1790);
let identitykey2 = mixnode.mixnode_details.bond_information.mix_node.identity_key
const identitykey2 =
mixnode.mixnode_details.bond_information.mix_node.identity_key;
if (typeof identitykey2 === "string") {
if (identitykey2.length === 43) {
return true
}
else expect(identitykey2).toHaveLength(44);
return true;
} else expect(identitykey2).toHaveLength(44);
}
let sphinx2 = mixnode.mixnode_details.bond_information.mix_node.sphinx_key
const sphinx2 =
mixnode.mixnode_details.bond_information.mix_node.sphinx_key;
if (typeof sphinx2 === "string") {
if (sphinx2.length === 43) {
return true
}
else expect(sphinx2).toHaveLength(44);
return true;
} else expect(sphinx2).toHaveLength(44);
}
//mixnode rewarding info
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBe("string")
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string")
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string")
expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe("string")
expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe("string")
expect(typeof mixnode.mixnode_details.rewarding_details.total_unit_reward).toBe("string")
expect(typeof mixnode.mixnode_details.rewarding_details.unit_delegation).toBe("string")
expect(typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number")
expect(typeof mixnode.mixnode_details.rewarding_details.unique_delegations).toBe("number")
expect(
typeof mixnode.mixnode_details.rewarding_details.cost_params
.profit_margin_percent
).toBe("string");
expect(
typeof mixnode.mixnode_details.rewarding_details.cost_params
.interval_operating_cost.denom
).toBe("string");
expect(
typeof mixnode.mixnode_details.rewarding_details.cost_params
.interval_operating_cost.amount
).toBe("string");
expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe(
"string"
);
expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe(
"string"
);
expect(
typeof mixnode.mixnode_details.rewarding_details.total_unit_reward
).toBe("string");
expect(
typeof mixnode.mixnode_details.rewarding_details.unit_delegation
).toBe("string");
expect(
typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch
).toBe("number");
expect(
typeof mixnode.mixnode_details.rewarding_details.unique_delegations
).toBe("number");
});
});
it("Get active mixnodes", async (): Promise<void> => {
const response = await contract.getActiveMixnodes();
response.forEach(function (mixnode) {
expect(mixnode.rewarding_details.cost_params.profit_margin_percent).toBeTruthy()
expect(typeof mixnode.bond_information.layer).toBe("number")
expect(
mixnode.rewarding_details.cost_params.profit_margin_percent
).toBeTruthy();
expect(typeof mixnode.bond_information.layer).toBe("number");
});
});
it("Get active mixnodes detailed", async (): Promise<void> => {
const response = await contract.getActiveMixnodesDetailed();
response.forEach(function (mixnode) {
expect(mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBeTruthy()
expect(
mixnode.mixnode_details.rewarding_details.cost_params
.profit_margin_percent
).toBeTruthy();
});
});
it("Get rewarded mixnodes", async (): Promise<void> => {
const response = await contract.getRewardedMixnodes();
response.forEach(function (mixnode) {
expect(mixnode.rewarding_details.last_rewarded_epoch).toBeTruthy()
expect(mixnode.rewarding_details.last_rewarded_epoch).toBeTruthy();
});
});
it("Get rewarded mixnodes detailed", async (): Promise<void> => {
const response = await contract.getRewardedMixnodesDetailed();
response.forEach(function (mixnode) {
expect(mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBeTruthy()
expect(
mixnode.mixnode_details.rewarding_details.last_rewarded_epoch
).toBeTruthy();
});
});
@@ -167,5 +238,4 @@ describe("Get mixnode data", (): void => {
expect(typeof value).toBe("number");
});
});
});
@@ -46,5 +46,4 @@ describe("Get gateway data", (): void => {
expect(typeof response.last_hour).toBe("number");
expect(typeof response.last_day).toBe("number");
});
});
@@ -40,9 +40,9 @@ describe("Get mixnode data", (): void => {
const response = await status.getMixnodeHistory(identity_key);
response.history.forEach((x) => {
console.log(x.date);
console.log(x.uptime);
})
expect(typeof x.date).toBe("string");
expect(typeof x.uptime).toBe("number");
});
expect(identity_key).toStrictEqual(response.mix_id);
expect(typeof response.owner).toBe("string");
@@ -67,7 +67,9 @@ describe("Get mixnode data", (): void => {
const identity_key = config.environmnetConfig.mix_id;
const response = await status.getMixnodeRewardComputation(identity_key);
expect(response.reward_params.interval.sybil_resistance).toStrictEqual("0.3");
expect(response.reward_params.interval.sybil_resistance).toStrictEqual(
"0.3"
);
expect(response.reward_params.active_set_size).toStrictEqual(240);
expect(typeof response.reward_params.interval.reward_pool).toBe("string");
});
@@ -79,42 +81,54 @@ describe("Get mixnode data", (): void => {
expect(typeof response.in_active).toBe("string");
});
it("Get all mixnodes inclusion probability", async (): Promise<void> => {
it("Get all mixnodes inclusion probabilities", async (): Promise<void> => {
const response = await status.getAllMixnodeInclusionProbability();
expect(response.inclusion_probabilities).toBeTruthy();
const array = response.inclusion_probabilities;
array.forEach((x) => {
expect(typeof x.in_reserve).toBe("number");
expect(typeof x.mix_id).toBe("number");
});
expect(typeof response.elapsed.nanos).toBe("number");
});
it("Get all mixnodes", async (): Promise<void> => {
const response = await status.getDetailedMixnodes();
expect(typeof response.stake_saturation).toBe("string");
response.forEach((x) => {
expect(typeof x.mixnode_details.bond_information.mix_id).toBe("number");
expect(typeof x.mixnode_details.bond_information.layer).toBe("number");
expect(typeof x.stake_saturation).toBe("string");
});
});
it("Get all rewarded mixnodes", async (): Promise<void> => {
const response = await status.getDetailedRewardedMixnodes();
expect(typeof response.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number");
response.forEach((x) => {
expect(typeof x.mixnode_details.bond_information.mix_id).toBe("number");
expect(typeof x.mixnode_details.bond_information.layer).toBe("number");
expect(typeof x.stake_saturation).toBe("string");
});
});
it("Get all active mixnodes", async (): Promise<void> => {
const response = await status.getDetailedActiveMixnodes();
expect(typeof response.mixnode_details.bond_information.layer).toBe("number");
response.forEach((x) => {
expect(typeof x.mixnode_details.bond_information.mix_id).toBe("number");
expect(typeof x.mixnode_details.bond_information.layer).toBe("number");
expect(typeof x.stake_saturation).toBe("string");
});
});
});
describe("Compute mixnode reward estimation", (): void => {
beforeAll(async (): Promise<void> => {
status = new Status();
config = ConfigHandler.getInstance();
});
it("with correct data", async (): Promise<void> => {
const response = await status.sendMixnodeRewardEstimatedComputation(8);
const body =
expect(typeof response.estimation.total_node_reward).toBe("string");
});
// TODO Fix this test
it.skip("with correct data", async (): Promise<void> => {
const response = await status.sendMixnodeRewardEstimatedComputation(8);
const body = expect(typeof response.estimation.total_node_reward).toBe(
"string"
);
});
});
@@ -12,4 +12,18 @@ export default class ContractCache extends APIClient {
});
return response.data;
}
public async getTotalSupplyValue(): Promise<number> {
const response = await this.restClient.sendGet({
route: `circulating-supply/total-supply-value`,
});
return response.data;
}
public async getCirculatingSupplyValue(): Promise<number> {
const response = await this.restClient.sendGet({
route: `circulating-supply/circulating-supply-value`,
});
return response.data;
}
}
+4 -2
View File
@@ -3,6 +3,8 @@ import {
AllGateways,
AllMixnodes,
EpochRewardParams,
BlacklistedGateways,
BlacklistedMixnodes,
CurrentEpoch,
} from "../types/ContractCacheTypes";
import { APIClient } from "./abstracts/APIClient";
@@ -62,14 +64,14 @@ export default class ContractCache extends APIClient {
return response.data;
}
public async getBlacklistedMixnodes(): Promise<[]> {
public async getBlacklistedMixnodes(): Promise<BlacklistedMixnodes[]> {
const response = await this.restClient.sendGet({
route: `mixnodes/blacklisted`,
});
return response.data;
}
public async getBlacklistedGateways(): Promise<[]> {
public async getBlacklistedGateways(): Promise<BlacklistedGateways[]> {
const response = await this.restClient.sendGet({
route: `gateways/blacklisted`,
});
+4 -4
View File
@@ -147,13 +147,13 @@ export default class Status extends APIClient {
public async getAllMixnodeInclusionProbability(): Promise<InclusionProbabilities> {
const response = await this.restClient.sendGet({
route: `/mixnodes/inclusion-probability`,
route: `/mixnodes/inclusion_probability`,
});
return response.data;
}
public async getDetailedMixnodes(): Promise<DetailedMixnodes> {
public async getDetailedMixnodes(): Promise<DetailedMixnodes[]> {
const response = await this.restClient.sendGet({
route: `/mixnodes/detailed`,
});
@@ -161,7 +161,7 @@ export default class Status extends APIClient {
return response.data;
}
public async getDetailedRewardedMixnodes(): Promise<DetailedMixnodes> {
public async getDetailedRewardedMixnodes(): Promise<DetailedMixnodes[]> {
const response = await this.restClient.sendGet({
route: `/mixnodes/rewarded/detailed`,
});
@@ -169,7 +169,7 @@ export default class Status extends APIClient {
return response.data;
}
public async getDetailedActiveMixnodes(): Promise<DetailedMixnodes> {
public async getDetailedActiveMixnodes(): Promise<DetailedMixnodes[]> {
const response = await this.restClient.sendGet({
route: `/mixnodes/active/detailed`,
});
@@ -1,11 +1,11 @@
export type Detailed = {
initial_supply: InitialSupply;
total_supply: TotalSupply;
mixmining_reserve: MixminingReserve;
vesting_tokens: VestingTokens;
circulating_supply: CirculatingSupply;
};
export type InitialSupply = {
export type TotalSupply = {
demon: "unym";
amount: string;
};
+32 -28
View File
@@ -1,9 +1,9 @@
export interface AllMixnodes {
export type AllMixnodes = {
bond_information: BondInformation;
rewarding_details: RewardingDetails;
}
};
export interface BondInformation {
export type BondInformation = {
mix_id: number;
owner: string;
original_pledge: OriginalPledge;
@@ -12,9 +12,9 @@ export interface BondInformation {
proxy: string;
bonding_height: number;
is_unbonding: boolean;
}
};
export interface RewardingDetails {
export type RewardingDetails = {
cost_params: CostParams;
operator: string;
delegates: string;
@@ -22,29 +22,29 @@ export interface RewardingDetails {
unit_delegation: string;
last_rewarded_epoch: number;
unique_delegations: number;
}
};
export interface CostParams {
export type CostParams = {
profit_margin_percent: string;
interval_operating_cost: IntervalOperatingCost;
}
};
export interface IntervalOperatingCost {
export type IntervalOperatingCost = {
denom: string;
amount: string;
}
};
export interface OriginalPledge {
export type OriginalPledge = {
denom: string;
amount: string;
}
};
export interface TotalDelegation {
export type TotalDelegation = {
denom: string;
amount: string;
}
};
export interface Mixnode {
export type Mixnode = {
host: string;
mix_port: number;
verloc_port: number;
@@ -52,9 +52,9 @@ export interface Mixnode {
sphinx_key: string;
identity_key: string;
version: string;
}
};
export interface MixnodeBond {
export type MixnodeBond = {
pledge_amount: OriginalPledge;
total_delegation: TotalDelegation;
owner: string;
@@ -63,9 +63,9 @@ export interface MixnodeBond {
mix_node: Mixnode;
proxy: string;
accumulated_rewards: string;
}
};
export interface MixnodesDetailed {
export type MixnodesDetailed = {
mixnode_details: AllMixnodes;
stake_saturation: string;
uncapped_stake_saturation: string;
@@ -73,7 +73,11 @@ export interface MixnodesDetailed {
estimated_operator_apy: string;
estimated_delegators_apy: string;
family: string;
}
};
export type BlacklistedMixnodes = {};
export type BlacklistedGateways = {};
export interface Gateway {
host: string;
@@ -93,13 +97,13 @@ export interface AllGateways {
proxy: string;
}
export interface EpochRewardParams {
export type EpochRewardParams = {
interval: Interval;
rewarded_set_size: number;
active_set_size: number;
}
};
export interface Interval {
export type Interval = {
reward_pool: string;
staking_supply: string;
staking_supply_scale_factor: string;
@@ -108,18 +112,18 @@ export interface Interval {
sybil_resistance: string;
active_set_work_factor: string;
interval_pool_emission: string;
}
};
export interface CurrentEpoch {
export type CurrentEpoch = {
id: number;
epochs_in_interval: number;
current_epoch_start: string;
current_epoch_id: number;
epoch_length: EpochLength;
total_elapsed_epochs: number;
}
};
export interface EpochLength {
export type EpochLength = {
secs: number;
nanos: number;
}
};
+1 -1
View File
@@ -20,6 +20,6 @@
"typeRoots": ["node_modules/@types"],
"alwaysStrict": true
},
"include": ["src/**/*", "tests/functional_test/*/*"],
"include": ["src/**/*", "functional_test/*/*"],
"exclude": ["unit_test/**/*"]
}
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "nym-api-requests"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bs58 = "0.4.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmwasm-std = { version = "1.0.0", default-features = false }
getset = "0.1.1"
schemars = { version = "0.8", features = ["preserve_order"] }
serde = { version = "1.0", features = ["derive"] }
ts-rs = { version = "6.1.2", optional = true }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" }
[features]
default = []
coconut = ["coconut-interface"]
generate-ts = ["ts-rs"]
@@ -0,0 +1,158 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmrs::AccountId;
use getset::{CopyGetters, Getters};
use serde::{Deserialize, Serialize};
use coconut_interface::{
error::CoconutInterfaceError, Attribute, Base58, BlindSignRequest, Credential, VerificationKey,
};
#[derive(Serialize, Deserialize, Getters, CopyGetters)]
pub struct VerifyCredentialBody {
#[getset(get = "pub")]
credential: Credential,
#[getset(get = "pub")]
proposal_id: u64,
#[getset(get = "pub")]
gateway_cosmos_addr: AccountId,
}
impl VerifyCredentialBody {
pub fn new(
credential: Credential,
proposal_id: u64,
gateway_cosmos_addr: AccountId,
) -> VerifyCredentialBody {
VerifyCredentialBody {
credential,
proposal_id,
gateway_cosmos_addr,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VerifyCredentialResponse {
pub verification_result: bool,
}
impl VerifyCredentialResponse {
pub fn new(verification_result: bool) -> Self {
VerifyCredentialResponse {
verification_result,
}
}
}
// All strings are base58 encoded representations of structs
#[derive(Clone, Serialize, Deserialize, Debug, Getters, CopyGetters)]
pub struct BlindSignRequestBody {
#[getset(get = "pub")]
blind_sign_request: BlindSignRequest,
#[getset(get = "pub")]
tx_hash: String,
#[getset(get = "pub")]
signature: String,
public_attributes: Vec<String>,
#[getset(get = "pub")]
public_attributes_plain: Vec<String>,
#[getset(get = "pub")]
total_params: u32,
}
impl BlindSignRequestBody {
pub fn new(
blind_sign_request: &BlindSignRequest,
tx_hash: String,
signature: String,
public_attributes: &[Attribute],
public_attributes_plain: Vec<String>,
total_params: u32,
) -> BlindSignRequestBody {
BlindSignRequestBody {
blind_sign_request: blind_sign_request.clone(),
tx_hash,
signature,
public_attributes: public_attributes
.iter()
.map(|attr| attr.to_bs58())
.collect(),
public_attributes_plain,
total_params,
}
}
pub fn public_attributes(&self) -> Vec<Attribute> {
self.public_attributes
.iter()
.map(|x| Attribute::try_from_bs58(x).unwrap())
.collect()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BlindedSignatureResponse {
pub remote_key: [u8; 32],
pub encrypted_signature: Vec<u8>,
}
impl BlindedSignatureResponse {
pub fn new(encrypted_signature: Vec<u8>, remote_key: [u8; 32]) -> BlindedSignatureResponse {
BlindedSignatureResponse {
encrypted_signature,
remote_key,
}
}
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, CoconutInterfaceError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.remote_key.to_vec();
bytes.extend_from_slice(&self.encrypted_signature);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoconutInterfaceError> {
if bytes.len() < 32 {
return Err(CoconutInterfaceError::InvalidByteLength(bytes.len(), 32));
}
let mut remote_key = [0u8; 32];
remote_key.copy_from_slice(&bytes[..32]);
let encrypted_signature = bytes[32..].to_vec();
Ok(BlindedSignatureResponse {
remote_key,
encrypted_signature,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct VerificationKeyResponse {
pub key: VerificationKey,
}
impl VerificationKeyResponse {
pub fn new(key: VerificationKey) -> VerificationKeyResponse {
VerificationKeyResponse { key }
}
}
#[derive(Serialize, Deserialize)]
pub struct CosmosAddressResponse {
pub addr: AccountId,
}
impl CosmosAddressResponse {
pub fn new(addr: AccountId) -> CosmosAddressResponse {
CosmosAddressResponse { addr }
}
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg(feature = "coconut")]
pub mod coconut;
pub mod models;
pub trait Deprecatable {
fn deprecate(self) -> Deprecated<Self>
where
Self: Sized,
{
self.into()
}
}
impl<T> Deprecatable for T {}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Deprecated<T> {
pub deprecated: bool,
#[serde(flatten)]
pub response: T,
}
impl<T> From<T> for Deprecated<T> {
fn from(response: T) -> Self {
Deprecated {
deprecated: true,
response,
}
}
}
@@ -0,0 +1,284 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Coin, Decimal};
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
use mixnet_contract_common::rewarding::RewardEstimate;
use mixnet_contract_common::{
IdentityKey, Interval, MixId, MixNode, Percent, RewardedSetNodeStatus,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{fmt, time::Duration};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct RequestError {
message: String,
}
impl RequestError {
pub fn new<S: Into<String>>(msg: S) -> Self {
RequestError {
message: msg.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/MixnodeStatus.ts")
)]
#[serde(rename_all = "snake_case")]
pub enum MixnodeStatus {
Active, // in both the active set and the rewarded set
Standby, // only in the rewarded set
Inactive, // in neither the rewarded set nor the active set, but is bonded
NotFound, // doesn't even exist in the bonded set
}
impl From<MixnodeStatus> for Option<RewardedSetNodeStatus> {
fn from(status: MixnodeStatus) -> Self {
match status {
MixnodeStatus::Active => Some(RewardedSetNodeStatus::Active),
MixnodeStatus::Standby => Some(RewardedSetNodeStatus::Standby),
MixnodeStatus::Inactive => None,
MixnodeStatus::NotFound => None,
}
}
}
impl MixnodeStatus {
pub fn is_active(&self) -> bool {
*self == MixnodeStatus::Active
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts")
)]
pub struct MixnodeCoreStatusResponse {
pub mix_id: MixId,
pub count: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts")
)]
pub struct GatewayCoreStatusResponse {
pub identity: String,
pub count: i32,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/MixnodeStatusResponse.ts")
)]
pub struct MixnodeStatusResponse {
pub status: MixnodeStatus,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct MixNodeBondAnnotated {
pub mixnode_details: MixNodeDetails,
pub stake_saturation: StakeSaturation,
pub uncapped_stake_saturation: StakeSaturation,
pub performance: Performance,
pub estimated_operator_apy: Decimal,
pub estimated_delegators_apy: Decimal,
pub family: Option<FamilyHead>,
}
impl MixNodeBondAnnotated {
pub fn mix_node(&self) -> &MixNode {
&self.mixnode_details.bond_information.mix_node
}
pub fn mix_id(&self) -> MixId {
self.mixnode_details.mix_id()
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ComputeRewardEstParam {
pub performance: Option<Performance>,
pub active_in_rewarded_set: Option<bool>,
pub pledge_amount: Option<u64>,
pub total_delegation: Option<u64>,
pub interval_operating_cost: Option<Coin>,
pub profit_margin_percent: Option<Percent>,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/RewardEstimationResponse.ts")
)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
pub struct RewardEstimationResponse {
pub estimation: RewardEstimate,
pub reward_params: RewardingParams,
pub epoch: Interval,
#[cfg_attr(feature = "generate-ts", ts(type = "number"))]
pub as_at: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct UptimeResponse {
pub mix_id: MixId,
pub avg_uptime: u8,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/StakeSaturationResponse.ts")
)]
pub struct StakeSaturationResponse {
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub saturation: StakeSaturation,
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub uncapped_saturation: StakeSaturation,
pub as_at: i64,
}
pub type StakeSaturation = Decimal;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/SelectionChance.ts")
)]
pub enum SelectionChance {
High,
Good,
Low,
}
impl From<f64> for SelectionChance {
fn from(p: f64) -> SelectionChance {
match p {
p if p >= 0.7 => SelectionChance::High,
p if p >= 0.3 => SelectionChance::Good,
_ => SelectionChance::Low,
}
}
}
impl From<Decimal> for SelectionChance {
fn from(p: Decimal) -> Self {
match p {
p if p >= Decimal::from_ratio(70u32, 100u32) => SelectionChance::High,
p if p >= Decimal::from_ratio(30u32, 100u32) => SelectionChance::Good,
_ => SelectionChance::Low,
}
}
}
impl fmt::Display for SelectionChance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SelectionChance::High => write!(f, "High"),
SelectionChance::Good => write!(f, "Good"),
SelectionChance::Low => write!(f, "Low"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts")
)]
pub struct InclusionProbabilityResponse {
pub in_active: SelectionChance,
pub in_reserve: SelectionChance,
}
impl fmt::Display for InclusionProbabilityResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"in_active: {}, in_reserve: {}",
self.in_active, self.in_reserve
)
}
}
#[derive(Clone, Serialize, schemars::JsonSchema)]
pub struct AllInclusionProbabilitiesResponse {
pub inclusion_probabilities: Vec<InclusionProbability>,
pub samples: u64,
pub elapsed: Duration,
pub delta_max: f64,
pub delta_l2: f64,
pub as_at: i64,
}
#[derive(Clone, Serialize, schemars::JsonSchema)]
pub struct InclusionProbability {
pub mix_id: MixId,
pub in_active: f64,
pub in_reserve: f64,
}
type Uptime = u8;
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct MixnodeStatusReportResponse {
pub mix_id: MixId,
pub identity: IdentityKey,
pub owner: String,
pub most_recent: Uptime,
pub last_hour: Uptime,
pub last_day: Uptime,
}
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct GatewayStatusReportResponse {
pub identity: String,
pub owner: String,
pub most_recent: Uptime,
pub last_hour: Uptime,
pub last_day: Uptime,
}
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct HistoricalUptimeResponse {
pub date: String,
pub uptime: Uptime,
}
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct MixnodeUptimeHistoryResponse {
pub mix_id: MixId,
pub identity: String,
pub owner: String,
pub history: Vec<HistoricalUptimeResponse>,
}
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct GatewayUptimeHistoryResponse {
pub identity: String,
pub owner: String,
pub history: Vec<HistoricalUptimeResponse>,
}
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -40,7 +40,7 @@ export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }>
)}
<AddressTooltip address={address} visible={!showEntireAddress}>
<Typography variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
<Typography data-testid="wallet-address" variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
{showEntireAddress ? address || '' : splice(6, address)}
</Typography>
</AddressTooltip>
+5 -2
View File
@@ -2,6 +2,7 @@ import React from 'react';
import { Button, Stack, TextField, Typography } from '@mui/material';
import { Check, ContentCopySharp } from '@mui/icons-material';
import { Warning } from './Warning';
import { MnemonicModal } from './Accounts/modals/MnemonicModal';
export const Mnemonic = ({
mnemonic,
@@ -14,18 +15,19 @@ export const Mnemonic = ({
}) => (
<Stack spacing={2} alignItems="center">
<Warning>
<Typography sx={{ textAlign: 'center' }}>
<Typography sx={{ textAlign: 'center' }} data-testid="below24word">
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
</Typography>
</Warning>
<TextField
label="Mnemonic"
id="mnemonicPhrase"
type="input"
value={mnemonic}
multiline
autoFocus={false}
fullWidth
inputProps={{
inputProps={{
style: {
height: '160px',
},
@@ -39,6 +41,7 @@ export const Mnemonic = ({
/>
<Button
data-testid="copyMnemonic"
color="inherit"
disableElevation
size="large"
+1
View File
@@ -103,6 +103,7 @@ export const Nav = () => {
<ListItem
disableGutters
key={label}
data-testid={label}
onClick={onClick}
sx={{
cursor: 'pointer',
@@ -16,7 +16,7 @@ const NetworkItem: FCWithChildren<{ title: string; isSelected: boolean; onSelect
isSelected,
onSelect,
}) => (
<ListItem button onClick={onSelect}>
<ListItem button onClick={onSelect} data-testid={title}>
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
<ListItemText>{title}</ListItemText>
</ListItem>
@@ -38,6 +38,7 @@ export const NetworkSelector = () => {
return (
<>
<Button
data-testid="networkEnv"
variant="text"
color="inherit"
sx={{ color: 'text.primary', fontSize: 14 }}
+6
View File
@@ -21,6 +21,8 @@ export const MnemonicInput: FCWithChildren<{
autoFocus
fullWidth
multiline={showMnemonic}
inputProps={{
"data-testid": "inputMnemonic" }}
InputLabelProps={{ shrink: true }}
sx={{
'input::-webkit-textfield-decoration-container': {
@@ -29,6 +31,7 @@ export const MnemonicInput: FCWithChildren<{
}}
/>
<FormControlLabel
data-testid="Reveal Mnemonic"
control={<Checkbox checked={Boolean(showMnemonic)} onChange={() => setShowMnemonic((show) => !show)} />}
label="Reveal my mnemonic"
/>
@@ -68,6 +71,9 @@ export const PasswordInput: FCWithChildren<{
),
}}
InputLabelProps={{ shrink: true }}
inputProps={{
"data-testid": label,
}}
/>
</Box>
{error && <Error message={error} />}
@@ -2,11 +2,11 @@ import React from 'react';
import { Typography } from '@mui/material';
export const Title = ({ title }: { title: string }) => (
<Typography sx={{ color: 'common.white', fontWeight: 600, fontSize: 20 }}>{title}</Typography>
<Typography data-testid={title} sx={{ color: 'common.white', fontWeight: 600, fontSize: 20 }}>{title}</Typography>
);
export const Subtitle = ({ subtitle }: { subtitle: string }) => (
<Typography sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
<Typography data-testid={subtitle} sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
);
export const SubtitleSlick = ({ subtitle }: { subtitle: string }) => (
@@ -53,7 +53,7 @@ export const WordTiles = ({
return (
<Grid container spacing={3} justifyContent="center">
{mnemonicWords.map(({ name, index, disabled }) => (
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })}>
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })} data-testid="mnemonicWordTile">
<WordTile
mnemonicWord={name}
index={showIndex ? index : undefined}
@@ -79,7 +79,7 @@ const HiddenWord = ({ mnemonicWord }: { mnemonicWord: THiddenMnemonicWord }) =>
</Box>
</Fade>
</Box>
<Typography>{mnemonicWord.index}.</Typography>
<Typography data-testid="wordIndex">{mnemonicWord.index}.</Typography>
</Stack>
);
@@ -20,6 +20,7 @@ export const ConfirmMnemonic = () => {
<Subtitle subtitle="Enter the mnemonic you wish to create a password for" />
<MnemonicInput mnemonic={localMnemonic} onUpdateMnemonic={(mnc) => setLocalMnemonic(mnc)} error={error} />
<Button
data-testid="nextToPasswordCreation"
size="large"
variant="contained"
fullWidth
@@ -37,6 +38,7 @@ export const ConfirmMnemonic = () => {
Next
</Button>
<Button
data-testid="backToMnemonicSignIn"
size="large"
color="inherit"
fullWidth
@@ -48,6 +48,7 @@ export const ConnectPassword = () => {
onUpdatePassword={(pswd) => setPassword(pswd)}
label="Password"
autoFocus
data-testid="Password"
/>
<PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} />
</>
@@ -55,8 +56,10 @@ export const ConnectPassword = () => {
password={confirmedPassword}
onUpdatePassword={(pswd) => setConfirmedPassword(pswd)}
label="Confirm password"
data-testid="Confirm Password"
/>
<Button
data-testid="createPasswordButton"
size="large"
variant="contained"
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
@@ -65,6 +68,7 @@ export const ConnectPassword = () => {
{isLoading ? <CircularProgress size={25} /> : 'Create password'}
</Button>
<Button
data-testid="backToStep1PasswordCreation"
size="large"
color="inherit"
onClick={() => {
@@ -23,6 +23,7 @@ export const CreateMnemonic = () => {
<Button
variant="contained"
data-testid="iSavedMnemonic"
color="primary"
disableElevation
size="large"
@@ -33,6 +34,7 @@ export const CreateMnemonic = () => {
I saved my mnemonic
</Button>
<Button
data-testid="backToWelcome"
onClick={() => {
resetState();
navigate(-1);
@@ -57,13 +57,14 @@ export const CreatePassword = () => {
/>
<Button
size="large"
data-testid="nextStorePassword"
variant="contained"
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
onClick={storePassword}
>
Next
</Button>
<Button size="large" color="info" onClick={handleSkip}>
<Button data-testid="skipPasswordAndSignInWithMnemonic" size="large" color="info" onClick={handleSkip}>
Skip and sign in with mnemonic
</Button>
</Stack>
@@ -11,18 +11,18 @@ export const ExistingAccount = () => {
<Title title="Welcome to Nym" />
<SubtitleSlick subtitle="NEXT GENERATION OF PRIVACY" />
<Stack spacing={2} sx={{ width: 300 }}>
<Button variant="contained" size="large" onClick={() => navigate('/sign-in-mnemonic')} fullWidth>
<Button variant="contained" size="large" onClick={() => navigate('/sign-in-mnemonic')} fullWidth data-testid="signInWithMnemonic">
Sign in with mnemonic
</Button>
<Typography sx={{ textAlign: 'center', fontWeight: 600 }}>or</Typography>
<Button variant="contained" size="large" fullWidth onClick={() => navigate('/sign-in-password')}>
<Button variant="contained" size="large" fullWidth onClick={() => navigate('/sign-in-password')} data-testid="signInWithPassword">
Sign in with password
</Button>
<Box display="flex" justifyContent="space-between">
<Button color="inherit" onClick={() => navigate('/')}>
<Button color="inherit" onClick={() => navigate('/')} data-testid="backToWelcomePage">
Back
</Button>
<Button color="info" onClick={() => navigate('/forgot-password')}>
<Button color="info" onClick={() => navigate('/forgot-password')} data-testid="forgotPassword">
Forgot password?
</Button>
</Box>
@@ -39,15 +39,15 @@ export const SignInMnemonic = () => {
>
<Stack spacing={2}>
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
<Button variant="contained" size="large" fullWidth type="submit">
<Button variant="contained" size="large" fullWidth type="submit" data-testid="signInWithMnemonicButton">
Sign in with mnemonic
</Button>
<Box display="flex" justifyContent={passwordExists ? 'center' : 'space-between'}>
<Button color="inherit" onClick={() => handlePageChange(-1)}>
<Button color="inherit" onClick={() => handlePageChange(-1)} data-testid="backToSignInOptions">
Back
</Button>
{!passwordExists && (
<Button color="info" onClick={() => handlePageChange('/confirm-mnemonic')}>
<Button color="info" onClick={() => handlePageChange('/confirm-mnemonic')} data-testid="goToCreatePassword">
Create a password
</Button>
)}
@@ -29,6 +29,7 @@ export const SignInPassword = () => {
autoFocus
/>
<Button
data-testid="signInPasswordButton"
variant="contained"
size="large"
fullWidth
@@ -38,6 +39,7 @@ export const SignInPassword = () => {
</Button>
<Box display="flex" justifyContent="space-between">
<Button
data-testid="skipAndSignInWithMnemonic"
color="inherit"
disableElevation
onClick={() => {
@@ -49,6 +51,7 @@ export const SignInPassword = () => {
</Button>
<Button
data-testid="forgotPasswordButton"
color="info"
onClick={() => {
setError(undefined);
@@ -56,10 +56,11 @@ export const VerifyMnemonic = () => {
size="large"
disabled={currentSelection !== numberOfRandomWords}
onClick={() => navigate('/create-password')}
data-testid="nextToStep3"
>
Next
</Button>
<Button color="inherit" fullWidth size="large" onClick={() => navigate(-1)}>
<Button color="inherit" fullWidth size="large" onClick={() => navigate(-1)} data-testid="backToStep1">
Back
</Button>
</Stack>
@@ -19,6 +19,7 @@ export const WelcomeContent: FCWithChildren<{}> = () => {
variant="contained"
size="large"
onClick={() => navigate('/existing-account')}
data-testid="signIn"
>
Sign in
</Button>
@@ -29,6 +30,7 @@ export const WelcomeContent: FCWithChildren<{}> = () => {
disableElevation
size="large"
onClick={() => navigate('/create-mnemonic')}
data-testid="createAccount"
>
Create account
</Button>
+1 -1
View File
@@ -27,7 +27,7 @@ export const BalanceCard = () => {
)}
{!userBalance.error && (
<Typography
data-testid="refresh-success"
data-testid="nym-balance"
sx={{
color: 'text.primary',
textTransform: 'uppercase',
+1 -1
View File
@@ -19,5 +19,5 @@
"@assets/*": ["../assets/*"]
}
},
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target"]
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target", "wallet-fe-tests"]
}
+114
View File
@@ -0,0 +1,114 @@
import Balance from '../test/pageobjects/balanceScreen';
import Auth from '../test/pageobjects/authScreens';
const userData = require('../common/user-data.json');
const deleteScript = require('../scripts/deletesavedwallet');
const savedWalletScript = require('../scripts/deletesavedwallet.ts');
class Helpers {
// clear wallet data, login, and navigate to QA network
freshMnemonicLoginQaNetwork = async () => {
await deleteScript;
await savedWalletScript;
// await Auth.loginWithMnemonic(userData.mnemonic)
await this.loginMnemonic();
await Balance.selectQa();
};
// login with a mnemonic
loginMnemonic = async () => {
var decodedmnemonic = this.decodeBase(userData.mnemonic);
await Auth.loginWithMnemonic(decodedmnemonic);
};
// click the mnemonic words by index position
// TO-DO find the best approach
mnemonicWordTileIndex = async () => {
let mnemonic = await browser.execute(() => {
// @ts-ignore: Object is possibly 'null'.
return document.getElementById('mnemonicPhrase').innerHTML;
});
let arrayMnemonic = mnemonic.split(' ');
await this.navigateAndClick(Auth.copyMnemonic);
await this.navigateAndClick(Auth.iSavedMnemonic);
// verify the mnemonic words in the correct order
let mnemonicWordTiles = await Auth.mnemonicWordTile;
let wordTileIndex = await Auth.wordIndex;
const wordsArray: any[] = [];
for (const word of mnemonicWordTiles) {
const wordText = await word.getText();
const index = arrayMnemonic.indexOf(wordText);
wordsArray.push({ word, index });
}
for (const index of wordTileIndex) {
const indexValue = await index.getText();
const match = wordsArray.find((word) => +word.index === +indexValue - 1);
if (match) {
await match.word.click();
}
}
const nextButton = await Auth.nextToStep3;
//something needs checking over here
const isNextDisabled = await nextButton.getAttribute('disabled');
expect(isNextDisabled).toBe(null);
await this.navigateAndClick(Auth.nextToStep3);
};
// decode user data file
decodeBase = (input) => {
const m = Buffer.from(input, 'base64').toString();
return m;
};
// common actions
navigateAndClick = async (element) => {
await element.waitForClickable({ timeout: 6000 });
await element.click();
};
elementVisible = async (element) => {
await element.waitForDisplayed({ timeout: 6000 });
};
elementGetText = async (element) => {
await element.getText(element);
};
elementClickable = async (element) => {
await element.toBeClickable({ timeout: 8000 });
};
addValueToTextField = async (element, value) => {
await element.addValue(value);
};
verifyStrictText = async (element, expectedText) => {
let error = await element.getText();
expect(error).toStrictEqual(expectedText);
};
verifyPartialText = async (element, expectedText) => {
let error = await element.getText();
expect(error).toContain(expectedText);
};
getAccountAddress = async () => {
// fix this in the future to make it generic
let address = await browser.execute(() => {
return document.querySelectorAll("[data-testid='wallet-address']")[0].innerHTML;
});
return address;
}
//removed those nasty methods as we can now get the correct txs fee from estimation
//add cleaner approach
}
export default new Helpers();
@@ -0,0 +1,41 @@
module.exports = {
//welcome, sign in, create account
homePageErrorMnemonic: 'Error parsing bip39 mnemonic',
signInWithoutMnemonic: 'A mnemonic must be provided',
signInRandomString: 'mnemonic has a word count that is not a multiple of 6:',
signInIncorrectMnemonic: 'mnemonic contains an unknown word',
incorrectMnemonicPasswordCreation: 'The mnemonic provided is not valid. Please check the mnemonic',
invalidPasswordOnSignIn: 'failed to decrypt the given data with the provided password',
signInWithoutPassword: 'A password must be provided',
failedToFindWalletFile: 'The wallet file is not found',
//headers
mnemonicSignIn: 'Enter a mnemonic to sign in',
passwordSignIn: 'Enter a password to sign in',
//homePage
qaNetwork: 'QA',
sandboxNetwork: 'Testnet Sandbox',
mainnetNetwork: 'Nym Mainnet',
noNym: '0 NYM',
//send
invalidRecipientAddress: '123',
recipientAddress: 'n17tj0a0w6v7r2dc54rnkzfza6s8hxs87rj273a5',
amountToSend: '1',
negativeAmount: '-1',
inferiorAmount: '0.0000001',
confirmedAmount: '1 NYM',
sendDetails: 'Send details',
// bond
host: '1.1.1.1',
version: '1.2.1',
// user incorrect data
incorrectMnemonic:
'bottom crime humble able antique rural donkey guess parent potato tongue truly way disagree exile zebra someone else heat giraffe note order sun cradle',
randomString: 'thisrandomstring',
password: 'updownUPDOWN~#$2',
incorrectPassword: '123notvalid',
};
@@ -0,0 +1,3 @@
{
"mnemonic": ""
}
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
{
"name": "webdriverio-tests",
"version": "0.1.0",
"description": "",
"private": true,
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@wdio/cli": "^7.25.4",
"@wdio/local-runner": "^7.16.16",
"@wdio/mocha-framework": "^7.16.15",
"@types/mocha": "^9.1.1",
"prettier": "2.5.1",
"typescript": "^4.6.2",
"@nymproject/nym-validator-client": "0.18.0",
"@wdio/allure-reporter": "^7.0.0"
},
"scripts": {
"test": "wdio run test/wdio.conf.ts",
"test-login": "wdio run test/wdio.conf.ts --suite login",
"test-signup": "wdio run test/wdio.conf.ts --suite signup",
"test-general": "wdio run test/wdio.conf.ts --suite general",
"test-balance": "wdio run test/wdio.conf.ts --suite balance",
"test-helper": "wdio run test/wdio.conf.ts --suite helper",
"test-delegation": "wdio run test/wdio.conf.ts --suite delegation",
"run:prettier": "prettier --write ."
}
}
@@ -0,0 +1,11 @@
const { exec } = require('child_process');
const os = require('os');
let homedir = os.homedir();
const doesFileExist = exec(`test -f ${homedir}/.local/share/nym-wallet/saved-wallet.json`, (err, stdout, stderr) => {
if (err) {
console.error(`${err.message}`);
return;
} else console.log('File deleted');
});
@@ -0,0 +1,5 @@
#!/bin/bash
#kill existing process
kill -9 "$(pgrep aux | grep -E "WebKitWeb|tauri-dri" | awk '{print $2}')"
exit 0;
@@ -0,0 +1,38 @@
class Nav {
get lightMode(): Promise<WebdriverIO.Element> {
return $("[data-testid='LightModeOutlinedIcon']");
}
get darkMode(): Promise<WebdriverIO.Element> {
return $("[data-testid='ModeNightOutlinedIcon']");
}
get terminalTitle(): Promise<WebdriverIO.Element> {
return $("[data-testid='terminal-header']");
}
get terminalIcon(): Promise<WebdriverIO.Element> {
return $("[data-testid='TerminalIcon']");
}
get balance(): Promise<WebdriverIO.Element> {
return $("[data-testid='Balance']");
}
get send(): Promise<WebdriverIO.Element> {
return $("[data-testid='Send']");
}
get receive(): Promise<WebdriverIO.Element> {
return $("[data-testid='Receive']");
}
get bond(): Promise<WebdriverIO.Element> {
return $("[data-testid='Bond']");
}
get unbond(): Promise<WebdriverIO.Element> {
return $("[data-testid='Unbond']");
}
get delegation(): Promise<WebdriverIO.Element> {
return $("[data-testid='Delegation']");
}
get closeIcon(): Promise<WebdriverIO.Element> {
return $("[data-testid='CloseIcon']");
}
}
export default new Nav();
@@ -0,0 +1,162 @@
import Balance from '../pageobjects/balanceScreen';
import Helper from '../../common/helper';
const deleteScript = require('../../scripts/deletesavedwallet');
class Auth {
//Welcome landing page
get signInButton(): Promise<WebdriverIO.Element> {
return $("[data-testid='signIn']");
}
get createAccount(): Promise<WebdriverIO.Element> {
return $("[data-testid='createAccount']");
}
// Existing account sign in option page
get signInMnemonic(): Promise<WebdriverIO.Element> {
return $("[data-testid='signInWithMnemonic']");
}
get signInPassword(): Promise<WebdriverIO.Element> {
return $("[data-testid='signInWithPassword']");
}
get backToWelcomePage(): Promise<WebdriverIO.Element> {
return $("[data-testid='backToWelcomePage']");
}
get forgotPassword(): Promise<WebdriverIO.Element> {
return $("[data-testid='forgotPassword']");
}
// Sign in with mnemonic page
get mnemonicLoginScreenHeader(): Promise<WebdriverIO.Element> {
return $("[data-testid='Enter a mnemonic to sign in']");
}
get mnemonicInput(): Promise<WebdriverIO.Element> {
return $("[data-testid='inputMnemonic']");
}
get signIn(): Promise<WebdriverIO.Element> {
return $("[data-testid='signInWithMnemonicButton']");
}
get backToSignInOptions(): Promise<WebdriverIO.Element> {
return $("[data-testid='backToSignInOptions']");
}
get revealMnemonic(): Promise<WebdriverIO.Element> {
return $("[data-testid='Reveal Mnemonic']");
}
get createPassword(): Promise<WebdriverIO.Element> {
return $("[data-testid='goToCreatePassword']");
}
// Create password step 1/2
get backToMnemonicSignIn(): Promise<WebdriverIO.Element> {
return $("[data-testid='backToMnemonicSignIn']");
}
get nextToPasswordCreation(): Promise<WebdriverIO.Element> {
return $("[data-testid='nextToPasswordCreation']");
}
// Create password step 2/2
get password(): Promise<WebdriverIO.Element> {
return $("[data-testid='Password']");
}
get confirmPassword(): Promise<WebdriverIO.Element> {
return $("[data-testid='Confirm password']");
}
get createPasswordButton(): Promise<WebdriverIO.Element> {
return $("[data-testid='createPasswordButton']");
}
get backToStep1PasswordCreation(): Promise<WebdriverIO.Element> {
return $("[data-testid='backToStep1PasswordCreation']");
}
// Create account step 1/3
get copyMnemonic(): Promise<WebdriverIO.Element> {
return $("[data-testid='copyMnemonic']");
}
get iSavedMnemonic(): Promise<WebdriverIO.Element> {
return $("[data-testid='iSavedMnemonic']");
}
get mnemonicPhrase(): Promise<WebdriverIO.Element> {
return $('mnemonicPhrase');
}
get backToWelcomePageFromCreate(): Promise<WebdriverIO.Element> {
return $("[data-testid='backToWelcome']");
}
// Create account step 2/3
get wordIndex(): Promise<WebdriverIO.ElementArray> {
return $$("[data-testid='wordIndex']");
}
get mnemonicWordTile(): Promise<WebdriverIO.ElementArray> {
return $$("[data-testid='mnemonicWordTile']");
}
get nextToStep3(): Promise<WebdriverIO.Element> {
return $("[data-testid='nextToStep3']");
}
get backToStep1(): Promise<WebdriverIO.Element> {
return $("[data-testid='backToStep1']");
}
// Create account step 3/3
get nextStorePassword(): Promise<WebdriverIO.Element> {
return $("[data-testid='nextStorePassword']");
}
get skipPasswordAndSignInWithMnemonic(): Promise<WebdriverIO.Element> {
return $("[data-testid='skipPasswordAndSignInWithMnemonic']");
}
// Enter password to sign in
get passwordLoginScreenHeader(): Promise<WebdriverIO.Element> {
return $("[data-testid='Enter a password to sign in']");
}
get enterPassword(): Promise<WebdriverIO.Element> {
return $("[data-testid='Enter password']");
}
get signInPasswordButton(): Promise<WebdriverIO.Element> {
return $("[data-testid='signInPasswordButton']");
}
get backToSignInOptionsFromPassword(): Promise<WebdriverIO.Element> {
return $("[data-testid='skipAndSignInWithMnemonic']");
}
get forgotPasswordButton(): Promise<WebdriverIO.Element> {
return $("[data-testid='forgotPasswordButton']");
}
// Errors
get error(): Promise<WebdriverIO.Element> {
return $("[data-testid='error']");
} //check
//TO-DO get this bit below working
getErrorMessage = async () => {
await (await this.error).waitForDisplayed({ timeout: 1500 });
await (await this.error).getText();
};
//login to the application
loginWithMnemonic = async (mnemonic) => {
await (await this.signInButton).click();
await (await this.signInMnemonic).click();
await (await this.mnemonicInput).waitForDisplayed();
await (await this.revealMnemonic).click();
await (await this.mnemonicInput).addValue(mnemonic);
await (await this.signIn).click();
await (await Balance.nymBalance).waitForDisplayed({ timeout: 4000 });
};
newMnemonicCreation = async () => {
deleteScript;
await Helper.navigateAndClick(this.createAccount);
await Helper.mnemonicWordTileIndex();
const nextButton = await this.nextToStep3;
const isNextDisabled = await nextButton.getAttribute('disabled');
expect(isNextDisabled).toBe(null);
await Helper.navigateAndClick(this.nextToStep3);
};
}
export default new Auth();
@@ -0,0 +1,38 @@
class Balance {
get balance(): Promise<WebdriverIO.Element> {
return $("[data-testid='Balance']");
}
get checkBalance(): Promise<WebdriverIO.Element> {
return $("[data-testid='check-balance']");
}
get nymBalance(): Promise<WebdriverIO.Element> {
return $("[data-testid='nym-balance']");
}
get copyAccountId(): Promise<WebdriverIO.Element> {
return $("[data-testid='ContentCopyIcon']");
}
get walletAddress(): Promise<WebdriverIO.Element> {
return $("[data-testid='wallet-address']");
}
get networkDropdown(): Promise<WebdriverIO.Element> {
return $("[data-testid='ArrowDropDownIcon']");
}
get networkEnv(): Promise<WebdriverIO.Element> {
return $("[data-testid='networkEnv']");
}
get networkSelectQa(): Promise<WebdriverIO.Element> {
return $("[data-testid='QA']");
}
selectQa = async () => {
await (await this.networkDropdown).waitForDisplayed({ timeout: 4000 });
await (await this.networkDropdown).click();
await (await this.networkSelectQa).waitForClickable({ timeout: 4000 });
await (await this.networkSelectQa).click();
await (await this.networkEnv).waitForClickable({ timeout: 2000 });
};
}
export default new Balance();
@@ -0,0 +1,23 @@
class Bond {
// Bonding
get bondTitle() {
return $("[data-testid='Bond']");
}
get mixnodeRadio() {
return $("[data-testid='mix-node']");
}
get gatewayRadio() {
return $("[data-testid='gate-way']");
}
get fundsAlert() {
return $("[data-testid='fundsAlert']");
}
// Unbonding
get unbondTitle() {
return $("[data-testid='Unbond']");
}
}
export default new Bond();
@@ -0,0 +1,13 @@
class Delegation {
get delegationTitle() {
return $("[data-testid='Delegation']");
}
get delegateStakeButton() {
return $("[data-testid='Delegate stake']");
}
get delegateModalHeader() {
return $("[data-testid='Delegate']");
}
}
export default new Delegation();
@@ -0,0 +1,7 @@
class Receive {
get receiveNymTitle() {
return $("[data-testid='Receive NYM']");
}
}
export default new Receive();
@@ -0,0 +1,46 @@
class Send {
// send nym form
get sendHeader() {
return $("[data-testid='Send']");
}
get recipientAddress() {
return $("[data-testid='recipientAddress']");
}
// get sendAmount() { return $("[data-testid='Amount']") }
get sendAmount() {
return $('#mui-5');
} // TO-DO fix this selector, using #mui-5 isn't a good solution
get next() {
return $("[data-testid='Next']");
}
// confirm transaction modal
get sendDetailsHeader() {
return $("[data-testid='Send details']");
}
get from() {
return $('/html/body/div[2]/div[3]/div[2]/div[1]/div[1]');
}
get to() {
return $('/html/body/div[2]/div[3]/div[2]/div[2]');
}
get amount() {
return $('/html/body/div[2]/div[3]/div[2]/div[3]');
}
get fee() {
return $('/html/body/div[2]/div[3]/div[2]/div[4]');
}
get confirm() {
return $("[data-testid='Confirm']");
}
// transaction sent
get viewOnBlockchain() {
return $("[data-testid='viewOnBlockchain']");
}
get done() {
return $("[data-testid='Done']");
}
}
export default new Send();
@@ -0,0 +1,34 @@
import Balance from '../../pageobjects/balanceScreen';
import Helper from '../../../common/helper';
const textConstants = require('../../../common/text-constants');
describe('Balance screen displays correctly', () => {
it('selecting qa network', async () => {
//log in
await Helper.loginMnemonic();
// select QA network
await Helper.navigateAndClick(Balance.networkDropdown);
await Helper.navigateAndClick(Balance.networkSelectQa);
// verifty QA network has been selected properly
await Helper.verifyStrictText(Balance.networkEnv, textConstants.qaNetwork);
});
it('copy the account id', async () => {
// ensure the account number contains *something*
await Helper.elementVisible(Balance.walletAddress);
let getaccountAddress = await Helper.getAccountAddress();
console.log(getaccountAddress);
await Helper.navigateAndClick(Balance.copyAccountId);
// disclaimer - I think if it's in clipboard we can use the below...
// let's try using the clipboard api here - TODO
// let clipboard = await browser.execute(() => {
//
// });
//
// })
});
});
@@ -0,0 +1,18 @@
import Balance from '../../pageobjects/balanceScreen';
import Auth from '../../pageobjects/authScreens';
import Nav from '../../pageobjects/appNavConstants';
import Delegation from '../../pageobjects/delegationScreen';
import Send from '../../pageobjects/sendScreen';
const Helper = require('../../../common/helper');
const textConstants = require('../../../common/text-constants');
const userData = require('../../../common/user-data.json');
describe('Delegate to a mixnode', () => {
it('entering an invalid node identity key', async () => {
//login and navigate to the screen
await Helper.freshMnemonicLoginQaNetwork();
await Helper.navigateAndClick(Nav.delegation);
await Helper.elementVisible(Delegation.delegationTitle);
// TO-DO enter an invalid node
});
});
@@ -0,0 +1,63 @@
import Auth from '../../pageobjects/authScreens';
import Nav from '../../pageobjects/appNavConstants';
import Balance from '../../pageobjects/balanceScreen';
import Send from '../../pageobjects/sendScreen';
import Receive from '../../pageobjects/receiveScreen';
import Bond from '../../pageobjects/bondScreen';
import Delegation from '../../pageobjects/delegationScreen';
const userData = require('../../../common/user-data.json');
const Helper = require('../../../common/helper');
describe('Nav Items behave correctly', () => {
it('switch from light to dark mode and back', async () => {
//log in()
await Helper.freshMnemonicLoginQaNetwork();
// click on different modes
await Helper.navigateAndClick(Nav.lightMode);
await Helper.navigateAndClick(Nav.darkMode);
await Helper.elementVisible(Nav.lightMode);
});
it('clicking terminal opens the modal', async () => {
// ensure the terminal button opens the terminal
await Helper.elementVisible(Nav.terminalIcon);
await Helper.navigateAndClick(Nav.terminalIcon);
await Helper.elementVisible(Nav.terminalTitle);
await Helper.verifyPartialText(Nav.terminalTitle, 'Terminal');
});
});
describe('Menu items lead to correct screen', () => {
//TO-DO none of this works
//check each menu item opens the right screen/modal
it('check Balance link works', async () => {
await Helper.navigateAndClick(Nav.balance);
await Helper.verifyPartialText(Balance.balance, 'Balance');
});
it('check Send link works', async () => {
await Helper.navigateAndClick(Nav.send);
await Helper.verifyPartialText(Send.sendHeader, 'Send');
await Helper.navigateAndClick(Nav.closeIcon);
});
it('check Receive link works', async () => {
await Helper.navigateAndClick(Nav.receive);
await Helper.verifyPartialText(Receive.receiveNymTitle, 'Receive NYM');
});
it('check Bond link works', async () => {
await Helper.navigateAndClick(Nav.bond);
await Helper.verifyPartialText(Bond.bondTitle, 'Bond');
});
it('check Unbond link works', async () => {
await Helper.navigateAndClick(Nav.unbond);
await Helper.verifyPartialText(Bond.unbondTitle, 'Unbond');
});
it('check Delegation link works', async () => {
await Helper.navigateAndClick(Nav.delegation);
await Helper.verifyPartialText(Delegation.delegationTitle, 'Delegation');
});
});
@@ -0,0 +1,57 @@
import Balance from '../../pageobjects/balanceScreen';
import Auth from '../../pageobjects/authScreens';
import Nav from '../../pageobjects/appNavConstants';
import Send from '../../pageobjects/sendScreen';
const Helper = require('../../../common/helper');
const textConstants = require('../../../common/text-constants');
const userData = require('../../../common/user-data.json');
describe.skip('Send modal functions correctly', () => {
it('entering an invalid recipient address shows error', async () => {
// sign in with mnemonic and select QA
await Helper.freshMnemonicLoginQaNetwork();
// click on send and check modal appears
await Helper.navigateAndClick(Nav.send);
await Helper.elementVisible(Send.sendHeader);
// add an invalid recipient address
await Helper.addValueToTextField(Send.recipientAddress, textConstants.invalidRecipientAddress);
// TO-DO -- question: should there not be an error message before clicking on Next to warn that the address is invalid?
});
it('entering an valid recipient address with negative amount value shows error', async () => {
await Helper.navigateAndClick(Send.recipientAddress);
// TO-DO figure out how to clear a text field before adding new value
await Send.recipientAddress.clearValue();
await Helper.addValueToTextField(Send.recipientAddress, userData.receiver_address);
await Helper.navigateAndClick(Send.sendAmount);
await Helper.addValueToTextField(Send.sendAmount, textConstants.negativeAmount);
//next button is still disabled and error message appears
const nextButton = await Send.next;
const isNextDisabled = await nextButton.getAttribute('disabled');
expect(isNextDisabled).toBe('true');
});
it('enter a valid recipient and value', async () => {
// enter valid data
await Helper.addValueToTextField(Send.recipientAddress, userData.receiver_address);
const getCurrentBalance = await (await Balance.nymBalance).getText();
await Helper.addValueToTextField(Send.sendAmount, textConstants.amountToSend);
// click on next and verify details
await Helper.navigateAndClick(Send.next);
const fee = await (await Send.fee).getText();
await Helper.verifyPartialText(Send.sendDetailsHeader, textConstants.sendDetails);
await Helper.verifyPartialText(Send.amount, textConstants.confirmedAmount);
await Helper.navigateAndClick(Send.confirm);
await Helper.elementVisible(Send.viewOnBlockchain);
await Helper.elementClickable(Send.done);
// calculate the transaction and verify it has been correctly executed
let sumCost = await Helper.calculateFees(getCurrentBalance, fee, textConstants.amountToSend, true);
const getNewBalance = await (await Balance.nymBalance).getText();
await Helper.navigateAndClick(Send.done);
// TO-DO the following fails with "TypeError: elem[prop] is not a function"
expect(getNewBalance).toEqual(sumCost);
});
});
@@ -0,0 +1,11 @@
import Auth from '../../pageobjects/authScreens';
describe('Create a new account and verify login', () => {
it('generate new mnemonic and verify mnemonic words', async () => {
// test to check new mnemonic creation
// will refine shortly
await browser.pause(1500);
await Auth.newMnemonicCreation();
});
});
@@ -0,0 +1,54 @@
import Auth from '../../pageobjects/authScreens';
import Balance from '../../pageobjects/balanceScreen';
import ValidatorClient from '@nymproject/nym-validator-client';
const textConstants = require('../../../common/text-constants');
const Helper = require('../../../common/helper');
describe('Wallet sign in functionality with mnemonic', () => {
it('get to the sign in with mnemonic screen', async () => {
// click through to reach the mnemonic sign in
await Helper.navigateAndClick(Auth.signInButton);
await Helper.navigateAndClick(Auth.signInMnemonic);
// verify you are on the right screen by confirming the header
await Helper.verifyStrictText(Auth.mnemonicLoginScreenHeader, textConstants.mnemonicSignIn);
});
it('sign in with no mnemonic throws error', async () => {
await Helper.navigateAndClick(Auth.signIn);
// wait for error
await Helper.elementVisible(Auth.error);
// verify error has the correct message
await Helper.verifyStrictText(Auth.error, textConstants.signInWithoutMnemonic);
});
it('sign in with incorrect mnemonic throws error', async () => {
// enter an incorrect mnemonic string
await Helper.navigateAndClick(Auth.revealMnemonic);
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.incorrectMnemonic);
await Helper.navigateAndClick(Auth.signIn);
// verifty error message is correct
await Helper.verifyPartialText(Auth.error, textConstants.signInIncorrectMnemonic);
});
it('sign in with random string throws error', async () => {
await Helper.navigateAndClick(Auth.revealMnemonic);
// enter a random string not in mnemonic "format"
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.randomString);
await Helper.navigateAndClick(Auth.signIn);
// verifty error message is correct
await Helper.verifyPartialText(Auth.error, textConstants.signInRandomString);
});
it('should sign in with valid credentials', async () => {
// create new mnemonic
const randomMnemonic = ValidatorClient.randomMnemonic();
// enter mnemonic
await Helper.navigateAndClick(Auth.revealMnemonic);
await Helper.addValueToTextField(Auth.mnemonicInput, randomMnemonic);
await Helper.navigateAndClick(Auth.signIn);
// verify successful login, balance is visible
await Helper.elementVisible(Balance.balance);
// TO-DO this value below is sometimes returning ""
await Helper.verifyStrictText(Balance.nymBalance, textConstants.noNym);
});
});
@@ -0,0 +1,98 @@
import Auth from '../../pageobjects/authScreens';
import Balance from '../../pageobjects/balanceScreen';
import ValidatorClient from '@nymproject/nym-validator-client';
const deleteScript = require('../../../scripts/deletesavedwallet');
const textConstants = require('../../../common/text-constants');
const Helper = require('../../../common/helper');
describe('Create password for existing account and use it to sign in', () => {
it('enter incorrect mnemonic', async () => {
//click through sign in
await Helper.navigateAndClick(Auth.signInButton);
await Helper.navigateAndClick(Auth.signInMnemonic);
//instead of entering mnemonic, click on create a password
await Helper.navigateAndClick(Auth.createPassword);
//enter incorrect mnemonic
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.incorrectMnemonic);
await Helper.navigateAndClick(Auth.nextToPasswordCreation);
// assert error message is correct
await Helper.verifyStrictText(Auth.error, textConstants.incorrectMnemonicPasswordCreation);
});
it('enter random string', async () => {
// enter random string as mnemonic
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.randomString);
await Helper.navigateAndClick(Auth.nextToPasswordCreation);
// assert error is correct
await Helper.verifyStrictText(Auth.error, textConstants.incorrectMnemonicPasswordCreation);
});
it('enter correct mnemonic', async () => {
// generate random mnemonic in the backend
const randomMnemonic = ValidatorClient.randomMnemonic();
// use it to continue with password creation flow
await Helper.navigateAndClick(Auth.backToMnemonicSignIn);
await Helper.navigateAndClick(Auth.createPassword);
await Helper.navigateAndClick(Auth.revealMnemonic);
await Helper.addValueToTextField(Auth.mnemonicInput, randomMnemonic);
await Helper.navigateAndClick(Auth.nextToPasswordCreation);
await Helper.elementVisible(Auth.password);
});
it('create an invalid password', async () => {
// type an invalid password in both fields
await Helper.addValueToTextField(Auth.password, textConstants.incorrectPassword);
await Helper.navigateAndClick(Auth.confirmPassword);
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.incorrectPassword);
// ensure the button to proceed is still disabled
const nextButton = await Auth.createPasswordButton;
const isNextDisabled = await nextButton.getAttribute('disabled');
expect(isNextDisabled).toBe('true');
});
it('create a valid password', async () => {
// type a valid password in both fields
await Helper.navigateAndClick(Auth.password);
await Helper.addValueToTextField(Auth.password, textConstants.password);
await Helper.navigateAndClick(Auth.confirmPassword);
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.password);
// verify the password is created and the next screen is visible
await Helper.navigateAndClick(Auth.createPasswordButton);
await Helper.verifyStrictText(Auth.passwordLoginScreenHeader, textConstants.passwordSignIn);
});
it('sign in with no password throws error', async () => {
//click sign without entering a password
await Helper.navigateAndClick(Auth.signInPasswordButton);
// wait for error
await Helper.elementVisible(Auth.error);
// verify error has the correct message
await Helper.verifyStrictText(Auth.error, textConstants.signInWithoutPassword);
});
it('sign in with invalid password throws error', async () => {
// enter invalid password
await Helper.addValueToTextField(Auth.enterPassword, textConstants.incorrectPassword);
await Helper.navigateAndClick(Auth.signInPasswordButton);
// wait for error
await Helper.elementVisible(Auth.error);
await Helper.verifyStrictText(Auth.error, textConstants.invalidPasswordOnSignIn);
});
});
describe('Wallet sign in functionality without creating password', () => {
it('sign in with invalid password and no saved wallet.json file throws error', async () => {
//click through sign without entering a password
await Helper.navigateAndClick(Auth.signInButton);
await Helper.navigateAndClick(Auth.signInPassword);
// enter invalid password
await Helper.addValueToTextField(Auth.enterPassword, textConstants.incorrectPassword);
await Helper.navigateAndClick(Auth.signInPasswordButton);
// wait for error
await Helper.elementVisible(Auth.error);
// verify error has the correct message
await Helper.verifyStrictText(Auth.error, textConstants.failedToFindWalletFile);
});
});
@@ -0,0 +1,72 @@
import Auth from '../../pageobjects/authScreens';
import Balance from '../../pageobjects/balanceScreen';
const textConstants = require('../../../common/text-constants');
const userData = require('../../../common/user-data.json');
const deleteScript = require('../../../scripts/deletesavedwallet');
const Helper = require('../../../common/helper');
// TO-DO figure out how to not repeat steps but also start a fresh test on each run
describe('Create a new account negative scenarios', () => {
it('generate new mnemonic and verify mnemonic words', async () => {
//create the new mnemoinc
await Auth.newMnemonicCreation();
});
it('click skip password', async () => {
// click on skip password creation
await Helper.navigateAndClick(Auth.nextToStep3);
await Helper.navigateAndClick(Auth.skipPasswordAndSignInWithMnemonic);
// can see mnemonic login page
await Helper.elementVisible(Auth.mnemonicInput);
await Helper.navigateAndClick(Auth.backToSignInOptions);
});
it('set up invalid password for new account', async () => {
// enter invalid password in both fields
await Helper.navigateAndClick(Auth.password);
await Helper.addValueToTextField(Auth.password, textConstants.incorrectPassword);
await Helper.navigateAndClick(Auth.confirmPassword);
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.incorrectPassword);
// verify that the 'next' button is still disabled
const nextButton = await Auth.nextStorePassword;
const isNextDisabled = await nextButton.getAttribute('disabled');
expect(isNextDisabled).toBe('true');
await browser.reloadSession();
});
});
describe.skip('Create a new account and verify login', () => {
it('generate new mnemonic and verify mnemonic words', async () => {
await Auth.newMnemonicCreation();
});
it('set up valid password for new account', async () => {
// enter a valid password in both fields
await Helper.navigateAndClick(Auth.password);
await Helper.addValueToTextField(Auth.password, textConstants.password);
await browser.pause(3000);
await Helper.navigateAndClick(Auth.confirmPassword);
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.password);
await browser.pause(3000);
// verify that the 'next' button is clickable
const nextButton = await Auth.nextStorePassword;
const isNextDisabled = await nextButton.getAttribute('disabled');
expect(isNextDisabled).toBe(null);
});
it('proceed to login with newly created password', async () => {
// login with a password
await Helper.navigateAndClick(Auth.nextStorePassword);
await Helper.navigateAndClick(Auth.enterPassword);
await Helper.addValueToTextField(Auth.enterPassword, textConstants.password);
await Helper.navigateAndClick(Auth.signInPasswordButton);
await Helper.elementVisible(Balance.balance);
//new accounts will always default to mainnet, so 0 balance
await Helper.verifyStrictText(Balance.nymBalance, textConstants.noNym);
});
});
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"moduleResolution": "node",
"types": ["node", "webdriverio/async", "@wdio/mocha-framework", "expect-webdriverio"],
"target": "ES5"
}
}
@@ -0,0 +1,99 @@
const os = require('os');
const path = require('path');
const deleteSavedWallet = require('../scripts/deletesavedwallet');
const { spawn, spawnSync } = require('child_process');
//insert path to binary
const nym_path = '../target/debug/nym-wallet';
// this one below will run the prod version aka without QA netwo0rk option
// const nym_path = '../target/release/nym-wallet'
let tauriDriver: any;
exports.config = {
autoCompileOpts: {
autoCompile: true,
tsNodeOpts: {
transpileOnly: true,
project: 'test/tsconfig.json',
},
},
specs: ['./test/specs/**/*.ts'],
suites: {
signup: ['./test/specs/signup/*.ts'],
login: ['./test/specs/login/*.ts'],
balance: ['./test/specs/balance/*.ts'],
general: ['./test/specs/general/*.ts'],
send: ['./test/specs/bond/*.ts'],
delegation: ['./test/specs/delegation/*.ts'],
helper: ['./test/specs/helpers/*.ts'],
},
exclude: [
// 'path/to/excluded/files'
],
maxInstances: 1,
capabilities: [
{
maxInstances: 1,
'tauri:options': {
application: nym_path,
},
},
],
// ===================
// Test Configurations
// ===================
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: 'error',
bail: 0,
framework: 'mocha',
// reporters: ['spec'],
mochaOpts: {
ui: 'bdd',
timeout: 60000,
},
// Reporting tool and settings
reporters: [
[
'allure',
{
outputDir: 'allure-results',
disableWebdriverStepsReporting: true,
disableWebdriverScreenshotsReporting: true,
// useCucumberStepReporter: true,
// disableMochaHooks: true,
},
],
],
// Things to run before/after each test session
onPrepare: () => {
let scriptpath = process.cwd() + '/scripts/killprocess.sh';
spawn('bash', [scriptpath]);
},
beforeSession: () => {
tauriDriver = spawn(path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), [], {
stdio: [null, process.stdout, process.stderr],
});
//before the session - if any issues arise with wallet data, let's remove existing saved files
deleteSavedWallet;
},
afterEach: function (test) {
if (test.error !== undefined) {
browser.takeScreenshot();
}
},
// clean up the `tauri-driver` process we spawned at the start of the session
afterSession: () => tauriDriver.kill(),
};
File diff suppressed because it is too large Load Diff
-86
View File
@@ -1,86 +0,0 @@
<!--
Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
SPDX-License-Identifier: Apache-2.0
-->
# Nym Wallet Webdriverio testsuite
A webdriverio test suite implementation using tauri driver
with a page object model design. This is to provide quick iterative feedback
on the UI of the nym wallet. Currently, tauri-driver is available to run on Windows and Linux machines.
## Installation prerequisites
- `Yarn`
- `NodeJS >= v16.8.0`
- `Rust & cargo >= v1.56.1`
- `tauri-driver`
- `That you have an existing mnemonic and you can login to the app`
- `Have the details listed below to provide the user-data.json file`
## Key Information
- Please read the instructions on the `nym/tauri-wallet/README.md` in the root of the project on how to build the application
- Please ensure you have the relevant Webdriver kits installed on your machine -
```
linux:
sudo apt-get install -y webkit2gtk-driver
```
```
windows:
download msedgedriver.exe from https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
```
please visit [Tauri Studio](https://tauri.studio/en/docs/usage/guides/webdriver/introduction), this will specify the additional drivers you need
- The path to run the application is set in the `wdio.conf.js` which lives in the root directory
- Before running the suite you need to build the application and check that the application has
built successfully, if so, you will have an executable sitting in the target directory in `tauri-wallet/target/*/nym_wallet` (refer to point 1)
- The suite will not be able to detect elements on screen if you select a release build, however you can run tests against a release target
## Installation & usage
- `test excution happens inside /webdriver directory`
- `test data needs to be provided inside the user-data.json`
- `check the wdio.conf.cjs to see the test execution along with the path location of the binary`
```
example:
//mnemonic is a base64 enconded value, which is your 24 character passphrase, these values are for illustration purposes
{
"mnemonic" : "dGhpcyBpcyBhIHBhc3NwaHJhc2UK",
"punk_address" : "punk1f3dzkhmunma5ze5q952daxca6371989189",
"receiver_address" : "punk1p0ce82jxxglpmutvhq4mdwgcwf4avm5n1821982",
"amount_to_send" : "1",
"identity_key_to_delegate_mix_node": "value",
"identity_key_to_delegate_gateway" : "value",
"delegate_amount" : "1"
}
```
- `yarn test:runall` - the first test run will take some time to spin up be patient
- You can run tests individually by passing through the script situated in the package.json for example `yarn test:newuser`
Tests are categorised and run by their pages, they follow a sequential flow, if one test case fails before the next execution it may derail the next test.
//todo improve in near future
## Test reporting
Currently the tests use allure reporting, the configuration can be altered in the `wdio.conf.cjs`. At present it takes snapshots of any failing tests, the test output run can be seen in the allure-results directory
Tests ouput:
- <guid-testuite.xml>
- <guid-attachment.png>
If any tests fail in their test run it will produce the stack trace error along with the test in question
## TODO
_Disclaimer_: Still WIP
Implement error handling/ beforeTest() - validating json file exists with data for test execution
Currently this is dev'd against a Linux based OS, not tested against windows yet.
-12
View File
@@ -1,12 +0,0 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "14",
},
},
],
],
};
@@ -1,30 +0,0 @@
module.exports = {
//receivePage
recievePageInformation:
"You can receive tokens by providing this address to the sender",
receivePageHeaderText: "Receive Nym",
//sendPage
sendPunk: "Send punk",
//homePage
homePageErrorMnemonic: "Error parsing bip39 mnemonic",
homePageSignIn: "Sign in",
createOne: "Create one",
walletSuccess:
"Please store your mnemonic in a safe place. You'll need it to access your wallet",
//bondPage // unbondPage
bondAlreadyNoded: "Looks like you already have a mixnode bonded.",
bondNodeHeaderText: "Bond a node or gateway",
unbondNodeHeaderText: "Unbond a mixnode or gateway",
unbondMixNodeText: "Looks like you already have a mixnode bonded.",
unbondMixNode: "UNBOND",
//delegatePage // undelegatePage
delegateHeaderText: "Delegate\nDelegate to mixnode",
nodeIdentityValidationText: "identity is a required field",
amountValidationText: "amount is a required field",
undelegateHeaderText: "Undelegate from a mixnode or gateway",
delegationComplete: "Delegation complete",
};
@@ -1,9 +0,0 @@
{
"mnemonic": "value",
"punk_address": "",
"receiver_address": "",
"amount_to_send": "",
"identity_key_to_delegate_mix_node": "",
"identity_key_to_delegate_gateway": "",
"delegate_amount": ""
}
@@ -1,43 +0,0 @@
class Helpers {
//helper to decode mnemonic so plain 24 character passphrase isn't in sight albeit it is presented when ruunning the scripts
//maybe a show passphrase toggle button?
decodeBase = async (input) => {
var m = Buffer.from(input, "base64").toString();
return m;
};
navigateAndClick = async (element) => {
await element.click();
};
scrollIntoView = async (element) => {
await element.scrollIntoView();
};
currentBalance = async (value) => {
return parseFloat(value.split(/\s+/)[0].toString()).toFixed(5);
};
//todo need to improve calculation - WIP
calculateFees = async (beforeBalance, transactionFee, amount, isSend) => {
let fee;
if (isSend) {
//send transaction
fee = transactionFee.split(/\s+/)[0];
} else {
//delegate transaction
fee = transactionFee.split(/\s+/)[3];
}
const currentBalance = beforeBalance.split(/\s+/)[0];
const castCurrentBalance = parseFloat(currentBalance).toFixed(5);
const transCost = +parseFloat(amount) + +parseFloat(fee).toFixed(5);
let sum = parseFloat(castCurrentBalance) - parseFloat(transCost);
return sum.toFixed(5);
};
}
module.exports = new Helpers();
-27
View File
@@ -1,27 +0,0 @@
{
"name": "tauri_nym_wallet",
"version": "1.0.0",
"private": false,
"license": "MIT",
"scripts": {
"test:runall": "wdio run wdio.conf.cjs",
"test:sendreceive": "wdio run wdio.conf.cjs --suite sendreceive",
"test:home": "wdio run wdio.conf.cjs --suite home",
"test:bond": "wdio run wdio.conf.cjs --suite bond",
"test:delegate": "wdio run wdio.conf.cjs --suite delegate",
"test:newuser": "wdio run wdio.conf.cjs --suite newuser",
"run:prettier": "prettier --write ."
},
"dependencies": {
"@types/node": "^16.11.0",
"@wdio/allure-reporter": "^7.16.1",
"@wdio/cli": "^7.9.1",
"@zxing/browser": "^0.0.9"
},
"devDependencies": {
"@wdio/local-runner": "^7.14.1",
"@wdio/mocha-framework": "^7.14.1",
"@wdio/spec-reporter": "^7.14.1",
"prettier": "2.4.1"
}
}
@@ -1,48 +0,0 @@
class WalletBond {
get header() {
return $(
"#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span.MuiTypography-root.MuiCardHeader-subheader.MuiTypography-subtitle1.MuiTypography-colorTextSecondary.MuiTypography-displayBlock"
);
}
get identityKey() {
return $("#identityKey");
}
get sphinxKey() {
return $("#sphinxKey");
}
get amountToBond() {
return $("#amount");
}
get hostInput() {
return $("#host");
}
get versionInput() {
return $("version");
}
get selectAdvancedOptions() {
return $("[type='checkbox']");
}
get mixPort() {
return $("#mixPort");
}
get verlocPort() {
return $("#verlocPort");
}
get httpApiPort() {
return $("#httpApiPort");
}
get bondButton() {
return $("[data-testid='bond-button']");
}
get unBondButton() {
return $("[data-testid='un-bond']");
}
get unBond() {
return $("[data-testid='bond-noded']");
}
get unBondWarning() {
return $("div.MuiAlert-message");
}
}
module.exports = new WalletBond();
@@ -1,24 +0,0 @@
class WalletCreate {
get createAccount() {
return $("[href='#']");
}
get create() {
return $("[data-testid='create-button']");
}
get accountCreatedSuccessfully() {
return $("[data-testid='mnemonic-warning']");
}
get walletMnemonicValue() {
return $("[data-testid='mnemonic-phrase']");
}
get punkAddress() {
return $("[data-testid='wallet-address']");
}
get backToSignIn() {
return $("[data-testid='sign-in-button']");
}
get signInButton() {
return $("[type='submit']");
}
}
module.exports = new WalletCreate();
@@ -1,60 +0,0 @@
class WalletDelegate {
get header() {
return $("[data-testid='Delegate']");
}
get nodeIdentity() {
return $("#identity");
}
get amountToDelegate() {
return $("#amount");
}
get identityValidation() {
return $("#identity-helper-text");
}
get amountToDelegateValidation() {
return $("#amount-helper-text");
}
get delegateStakeButton() {
return $("[data-testid='delegate-button']");
}
get mixNodeRadioButton() {
return $("[data-testid='mix-node']");
}
get gateWayRadioButton() {
return $("[data-testid='gate-way']");
}
get successfullyDelegate() {
return $("[data-testid='delegate-success']");
}
get finishButton() {
return $("[data-testid='finish-button']");
}
get transactionFeeAmount() {
return $("[data-testid='fee-amount']");
}
get accountBalance() {
return $("[data-testid='account-balance']");
}
//Undelegate
get unDelegateHeader() {
return $("[data-testid='Undelegate']");
}
get unNodeIdentity() {
return $("[name='identity']");
}
get unDelegateFeeText() {
return $("[data-testid='fee-amount']");
}
get unDelegateGatewayRadioButton() {
return $("[data-testid='gate-way']");
}
get unMixNodeRadioButton() {
return $("[data-testid='mix-node']");
}
get unDelegateButton() {
return $("[data-testid='submit-button']");
}
}
module.exports = new WalletDelegate();
@@ -1,42 +0,0 @@
class WalletHome {
get balanceCheck() {
return $(
"#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span"
);
}
get punkBalance() {
return $("");
}
get punkAddress() {
return $("[data-testid='wallet-address']");
}
get accountBalance() {
return $("[data-testid='account-balance']");
}
get balanceButton() {
return $("[href='/balance']");
}
get sendButton() {
return $("[href='/send']");
}
get receiveButton() {
return $("[href='/receive']");
}
get bondButton() {
return $("[href='/bond']");
}
get unBondButton() {
return $("[href='/unbond']");
}
get delegateButton() {
return $("[href='/delegate']");
}
get unDelegateButton() {
return $("[href='/undelegate']");
}
get logOutButton() {
return $("[data-testid='log-out']");
}
}
module.exports = new WalletHome();
@@ -1,31 +0,0 @@
class WalletLogin {
get signInLabel() {
return $("[data-testid='sign-in']");
}
get mnemonic() {
return $("#mnemonic");
}
get signInButton() {
return $("[type='submit']");
}
get errorValidation() {
return $("[class='MuiAlert-message']");
}
get accountBalance() {
return $("[data-test-id='account-balance']");
}
get accountBalanceText() {
return $("[class='MuiAlert-message']");
}
get walletAddress() {
return $("[data-testid='wallet-address']");
}
//login to the application
enterMnemonic = async (mnemonic) => {
await this.mnemonic.addValue(mnemonic);
await this.signInButton.click();
await this.accountBalance.isExisting();
};
}
module.exports = new WalletLogin();
@@ -1,37 +0,0 @@
class WalletReceive {
get receiveNymHeader() {
return $(
"#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span"
);
}
get receiveNymText() {
return $("[data-testid='receive-nym']");
}
get walletAddress() {
return $("[data-testid='client-address']");
}
get copyButton() {
return $("[data-testid='copy-button']");
}
get qrCode() {
return $("[data-testid='qr-code']");
}
WaitForButtonChangeOnCopy = async () => {
await this.copyButton.click();
await this.copyButton.waitForDisplayed({ timeout: 1500 });
await this.copyButton.waitUntil(
async function () {
return (await this.getText()) === "COPIED";
},
{
timeout: 1500,
timeoutMsg: "expected text to be different after 1.5s",
}
);
};
}
module.exports = new WalletReceive();
@@ -1,52 +0,0 @@
class WalletSend {
get fromAddress() {
return $("#from");
}
get toAddress() {
return $("#to");
}
get amount() {
return $("#amount");
}
get nextButton() {
return $("[data-testid='button");
}
get sendHeader() {
return $("[data-testid='Send punk']");
}
get accountBalance() {
return $("[data-testid='account-balance']");
}
get amountReviewAndSend() {
return $("[data-testid='Amount']");
}
get toAddressReviewAndSend() {
return $("[data-testid='To']");
}
get fromAddressReviewAndSend() {
return $("[data-testid='From']");
}
get transferFeeAmount() {
return $("[data-testid='Transfer fee']");
}
get reviewAndSendBackButton() {
return $("[data-testid='back-button']");
}
get sendButton() {
return $("[data-testid='button']");
}
get transactionComplete() {
return $("[data-testid='transaction-complete']");
}
get transactionCompleteRecipient() {
return $("[data-testid='to-address']");
}
get transactionCompleteAmount() {
return $("[data-testid='send-amount']");
}
get finishButton() {
return $("[data-testid='button']");
}
}
module.exports = new WalletSend();
@@ -1,22 +0,0 @@
class WallentUndelegate {
get transactionFee() {
return $("[data-testid='fee-amount']");
}
get mixNodeRadioButton() {
return $("[value='mixnode']");
}
get gatewayRadionButton() {
return $("[value='gateway']");
}
get nodeIdentity() {
return $("#mui-55011");
}
get identityHelper() {
return $("#identity-helper-text");
}
get delegateButton() {
return $("[data-testid='submit-button']");
}
}
module.exports = new WallentUndelegate();
@@ -1,54 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const textConstants = require("../../../common/constants/text-constants");
const walletHomepage = require("../../pages/wallet.homepage");
const bondPage = require("../../pages/wallet.bond");
describe("bonding and unbonding nodes", () => {
it("should have a node already bonded and validate no input fields are enabled", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.bondButton);
await helper.scrollIntoView(bondPage.selectAdvancedOptions);
await bondPage.selectAdvancedOptions.click();
//as bond node is mixed expect all the fields to be disabled
const getText = await bondPage.header.getText();
const getIdentity = await bondPage.identityKey.isEnabled();
const getSphinxKey = await bondPage.sphinxKey.isEnabled();
const amountToBond = await bondPage.amountToBond.isEnabled();
const hostInput = await bondPage.hostInput.isEnabled();
const verlocPort = await bondPage.verlocPort.isEnabled();
const httpApiPort = await bondPage.httpApiPort.isEnabled();
const mixPort = await bondPage.mixPort.isEnabled();
//assert all field are not functional
expect(getText).toEqual(textConstants.bondNodeHeaderText);
expect(getIdentity).toEqual(false);
expect(getSphinxKey).toEqual(false);
expect(amountToBond).toEqual(false);
expect(hostInput).toEqual(false);
expect(verlocPort).toEqual(false);
expect(httpApiPort).toEqual(false);
expect(mixPort).toEqual(false);
});
it("unbond mix monde screen should be present with the option to unbond", async () => {
//we do not want to unbond our node, check that elements are selectable
await helper.scrollIntoView(walletHomepage.unBondButton);
await helper.navigateAndClick(walletHomepage.unBondButton);
const getText = await bondPage.header.getText();
const unbondText = await bondPage.unBondWarning.getText();
await bondPage.unBondButton.isClickable();
//assert all field are not functional
expect(getText).toEqual(textConstants.unbondNodeHeaderText);
expect(unbondText).toEqual(textConstants.unbondMixNodeText);
});
});
@@ -1,108 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const textConstants = require("../../../common/constants/text-constants");
const walletHomepage = require("../../pages/wallet.homepage");
const delegatePage = require("../../pages/wallet.delegate");
describe("delegate to a mix node or gateway", () => {
it("ensure that fields are enabled for existing user", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.delegateButton);
const getText = await delegatePage.header.getText();
expect(getText).toEqual(textConstants.delegateHeaderText);
});
it("submitting the form without input prompts validation errors", async () => {
await delegatePage.delegateStakeButton.click();
const getIdentityValidation =
await delegatePage.identityValidation.getText();
const getAmountValidation =
await delegatePage.amountToDelegateValidation.getText();
expect(getIdentityValidation).toEqual(
textConstants.nodeIdentityValidationText
);
expect(getAmountValidation).toEqual(textConstants.amountValidationText);
});
it("input delegate amount to a mix node then broadcast the transaction then check account balances", async () => {
const balanceText = await delegatePage.accountBalance.getText();
const getTransfeeAmount = await delegatePage.transactionFeeAmount.getText();
await delegatePage.nodeIdentity.setValue(
userData.identity_key_to_delegate_mix_node
);
await delegatePage.amountToDelegate.setValue(userData.delegate_amount);
//transfer fee + amount delegation
const sumCost = await helper.calculateFees(
balanceText,
getTransfeeAmount,
userData.delegate_amount,
false
);
await delegatePage.delegateStakeButton.click();
await delegatePage.successfullyDelegate.waitForClickable({
timeout: 10000,
});
const getConfirmationText =
await delegatePage.successfullyDelegate.getText();
expect(getConfirmationText).toContain(textConstants.delegationComplete);
const availablePunk = await delegatePage.accountBalance.getText();
//expect new account balance - the fee calculation above
await delegatePage.finishButton.click();
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost);
});
it("input amount to stake to a gateway then broadcast the transaction then check account balances", async () => {
const balanceText = await delegatePage.accountBalance.getText();
const getTransfeeAmount = await delegatePage.transactionFeeAmount.getText();
await delegatePage.gateWayRadioButton.click();
await delegatePage.nodeIdentity.setValue(
userData.identity_key_to_delegate_gateway
);
await delegatePage.amountToDelegate.setValue(userData.delegate_amount);
//transfer fee + amount delegation
const sumCost = await helper.calculateFees(
balanceText,
getTransfeeAmount,
userData.delegate_amount,
false
);
await delegatePage.delegateStakeButton.click();
await delegatePage.successfullyDelegate.waitForClickable({
timeout: 10000,
});
const getConfirmationText =
await delegatePage.successfullyDelegate.getText();
expect(getConfirmationText).toContain(textConstants.delegationComplete);
const availablePunk = await delegatePage.accountBalance.getText();
//expect new account balance - the fee calculation above
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost);
});
});
@@ -1,45 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const homepPage = require("../../pages/wallet.homepage");
const textConstants = require("../../../common/constants/text-constants");
describe("wallet splash screen", () => {
it("should have the sign in header present", async () => {
const signInText = await walletLogin.signInLabel.getText();
expect(signInText).toEqual(textConstants.homePageSignIn);
});
it("submitting the sign in button with no input throws a validation error", async () => {
await walletLogin.signInButton.click();
const errorResponseText = await walletLogin.errorValidation.getText();
expect(errorResponseText).toEqual(textConstants.homePageErrorMnemonic);
});
//currently the punk_address is not fully displayed on the wallet UI
//trim the punk address
it("successfully input mnemonic and log in", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await walletLogin.walletAddress.waitForEnabled({ timeout: 5000 });
const getWalletAddress = await walletLogin.walletAddress.getText();
//currently 35 characters are displayed along with three ...
//current hack we can assume this is the correct wallet
const walletTruncated = userData.punk_address.substring(0, 35);
expect(walletTruncated + "...").toContain(getWalletAddress);
});
it("successfully log out the application", async () => {
await helper.scrollIntoView(homepPage.logOutButton);
await homepPage.logOutButton.click();
await walletLogin.signInLabel.waitForEnabled({ timeout: 1500 });
expect(await walletLogin.signInLabel.isDisplayed()).toEqual(true);
});
});
@@ -1,28 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const textConstants = require("../../../common/constants/text-constants");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const receive = require("../../pages/wallet.receive");
const walletHomepage = require("../../pages/wallet.homepage");
describe("provide the relevant information about a user nym wallet address", () => {
it("should have the receivers address and a qr code present", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.receiveButton);
await receive.receiveNymHeader.waitForDisplayed({ timeout: 1500 });
await receive.WaitForButtonChangeOnCopy();
const textHeader = await receive.receiveNymHeader.getText();
const getInformationText = await receive.receiveNymText.getText();
const getPunkAddress = await receive.walletAddress.getText();
expect(getPunkAddress).toEqual(userData.punk_address);
expect(getInformationText).toEqual(textConstants.recievePageInformation);
expect(textConstants.receivePageHeaderText).toEqual(textHeader);
});
});
@@ -1,55 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const textConstants = require("../../../common/constants/text-constants");
const walletLogin = require("../../pages/wallet.login");
const sendWallet = require("../../pages/wallet.send");
const walletHomepage = require("../../pages/wallet.homepage");
describe("send punk to another a wallet", () => {
it("expect send screen to display the data", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.sendButton);
const textHeader = await sendWallet.sendHeader.getText();
expect(textHeader).toContain(textConstants.sendPunk);
});
it("send funds correctly to another punk address", async () => {
//already logged in due to the previous test
const getCurrentBalance = await walletHomepage.accountBalance.getText();
await sendWallet.toAddress.addValue(userData.receiver_address);
await sendWallet.amount.addValue(userData.amount_to_send);
await sendWallet.nextButton.waitForEnabled({ timeout: 3000 });
await sendWallet.nextButton.click();
const transFee = await sendWallet.transferFeeAmount.getText();
await sendWallet.sendButton.click();
await sendWallet.finishButton.waitForClickable({ timeout: 10000 });
let sumCost = await helper.calculateFees(
getCurrentBalance,
transFee,
userData.amount_to_send,
true
);
await walletHomepage.accountBalance.isDisplayed();
const availablePunk = await walletHomepage.accountBalance.getText();
await sendWallet.finishButton.click();
//expect new account balance - the fee calculation above
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost);
});
});
@@ -1,32 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const walletHomepage = require("../../pages/wallet.homepage");
const unDelegatePage = require("../../pages/wallet.delegate");
describe("un-delegate a mix node or gateway", () => {
it("ensure that fields are enabled for existing user", async () => {
//we are ensuring that the fields are selectable for undelegation
//not proceeding to undelegate a node or gateway
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.scrollIntoView(walletHomepage.unDelegateButton);
await helper.navigateAndClick(walletHomepage.unDelegateButton);
await unDelegatePage.unDelegateButton.waitForClickable({ timeout: 1500 });
await unDelegatePage.unDelegateButton.isEnabled();
await unDelegatePage.unDelegateGatewayRadioButton.click();
await unDelegatePage.unDelegateGatewayRadioButton.isSelected();
const mixNodeRadioButton =
await unDelegatePage.unMixNodeRadioButton.isSelected();
expect(mixNodeRadioButton).toEqual(false);
});
});
@@ -1,39 +0,0 @@
const walletLogin = require("../../pages/wallet.login");
const walletSignUp = require("../../pages/wallet.create");
const textConstants = require("../../../common/constants/text-constants");
describe("non existing wallet holder", () => {
//wallet mnemonic gets pushed here
const DATA = [];
it("create a new account and wallet", async () => {
const signInText = await walletLogin.signInLabel.getText();
expect(signInText).toEqual(textConstants.homePageSignIn);
await walletSignUp.createAccount.click();
//wallet generation takes some time - apply wait
await walletSignUp.create.click();
await walletSignUp.accountCreatedSuccessfully.waitForEnabled({
timeout: 10000,
});
const getWalletText = await walletSignUp.punkAddress.getText();
expect(getWalletText.length).toEqual(43);
const accountCreated =
await walletSignUp.accountCreatedSuccessfully.getText();
expect(accountCreated).toEqual(textConstants.walletSuccess);
const getMnemonic = await walletSignUp.walletMnemonicValue.getText();
DATA.push(getMnemonic);
});
it("navigate back to sign in screen and validate mnemonic works", async () => {
await walletSignUp.backToSignIn.click();
await walletLogin.enterMnemonic(DATA[0]);
await walletLogin.walletAddress.isDisplayed();
});
});
-93
View File
@@ -1,93 +0,0 @@
const os = require("os");
const path = require("path");
const { spawn, spawnSync } = require("child_process");
//insert path to binary
const nym_path = "../target/release/nym-wallet";
exports.config = {
//run sequentially, as using one default user may cause issues for parallel test runs for now
specs: [
"./tests/specs/existinguser/test.wallet.home.js",
"./tests/specs/existinguser/test.wallet.send.js",
"./tests/specs/existinguser/test.wallet.receive.js",
"./tests/specs/existinguser/test.wallet.bond.js",
"./tests/specs/existinguser/test.wallet.delegate.js",
"./tests/specs/newuser/test.wallet.create.js",
],
//run tests by providing --suite {{login}}
suites: {
home: ["./tests/specs/existinguser/test.wallet.home.js"],
sendreceive: [
"./tests/specs/existinguser/test.wallet.send.js",
"./tests/specs/existinguser/test.wallet.receive.js",
],
bond: ["./tests/specs/existinguser/test.wallet.bond.js"],
delegate: [
"./tests/specs/existinguser/test.wallet.delegate.js",
"./tests/specs/existinguser/test.wallet.undelegate.js",
],
newuser: ["./tests/specs/newuser/test.wallet.create.js"],
},
maxInstances: 1,
capabilities: [
{
maxInstances: 1,
"tauri:options": {
application: nym_path,
},
},
],
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here
// Level of logging verbosity: trace | debug | info | warn | error | silent
bail: 0,
framework: "mocha",
reporters: ["spec"],
mochaOpts: {
ui: "bdd",
timeout: 60000,
},
logLevel: "silent",
// ===================
// Test Reporters
// ===================
reporters: [
[
"allure",
{
outputDir: "allure-results",
disableWebdriverStepsReporting: true,
disableWebdriverScreenshotsReporting: true,
},
],
],
// this is documentented in the readme - you will need to build the project first
// ensure the rust project is built since we expect this binary to exist for the webdriver sessions
//onPrepare: () => spawnSync("cargo", ["build", "--release"]),
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests
beforeSession: () =>
(tauriDriver = spawn(
path.resolve(os.homedir(), ".cargo", "bin", "tauri-driver"),
[],
{ stdio: [null, process.stdout, process.stderr] }
)),
afterTest: function (
test,
context,
{ error, result, duration, passed, retries }
) {
if (error) {
browser.takeScreenshot();
}
},
// clean up the `tauri-driver` process we spawned at the start of the session
afterSession: () => tauriDriver.kill(),
};
BIN
View File
Binary file not shown.