mirror of
https://github.com/haveno-dex/haveno-ts.git
synced 2025-02-03 09:59:54 -05:00
add ability to start haveno processes from tests
test making or taking offer with insufficient unlocked balance
This commit is contained in:
parent
5dc5fe45d7
commit
fe2c715d80
@ -30,7 +30,7 @@ Running the [top-level API tests](./src/HavenoDaemon.test.ts) is a great way to
|
|||||||
|
|
||||||
1. [Run a local Haveno test network](https://github.com/haveno-dex/haveno/blob/master/docs/installing.md), running Alice and Bob as daemons with `make alice-daemon` and `make bob-daemon`.
|
1. [Run a local Haveno test network](https://github.com/haveno-dex/haveno/blob/master/docs/installing.md), running Alice and Bob as daemons with `make alice-daemon` and `make bob-daemon`.
|
||||||
2. Clone this project to the same parent directory as the haveno project: `git clone https://github.com/haveno-dex/haveno-ui-poc`
|
2. Clone this project to the same parent directory as the haveno project: `git clone https://github.com/haveno-dex/haveno-ui-poc`
|
||||||
3. In a new terminal, start envoy with the config in haveno-ui-poc/config/envoy.test.yaml (change absolute path for your system): `docker run --rm --add-host host.docker.internal:host-gateway -it -v ~/git/haveno-ui-poc/config/envoy.test.yaml:/envoy.test.yaml -p 8080:8080 -p 8081:8081 envoyproxy/envoy-dev:8a2143613d43d17d1eb35a24b4a4a4c432215606 -c /envoy.test.yaml`
|
3. In a new terminal, start envoy with the config in haveno-ui-poc/config/envoy.test.yaml (change absolute path for your system): `docker run --rm --add-host host.docker.internal:host-gateway -it -v ~/git/haveno-ui-poc/config/envoy.test.yaml:/envoy.test.yaml -p 8080:8080 -p 8081:8081 -p 8082:8082 -p 8083:8083 -p 8084:8084 -p 8085:8085 -p 8086:8086 envoyproxy/envoy-dev:8a2143613d43d17d1eb35a24b4a4a4c432215606 -c /envoy.test.yaml`
|
||||||
4. In a new terminal, start the funding wallet. This wallet will be automatically funded in order to fund Alice and Bob during the tests.<br>For example: `cd ~/git/haveno && make funding-wallet`.
|
4. In a new terminal, start the funding wallet. This wallet will be automatically funded in order to fund Alice and Bob during the tests.<br>For example: `cd ~/git/haveno && make funding-wallet`.
|
||||||
5. Install protobuf for your system:<br>
|
5. Install protobuf for your system:<br>
|
||||||
mac: `brew install protobuf`<br>
|
mac: `brew install protobuf`<br>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# envoy configuration to test with alice and bob trader instances
|
# envoy configuration to test with haveno instances
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
access_log_path: /tmp/admin_access.log
|
access_log_path: /tmp/admin_access.log
|
||||||
@ -73,6 +73,171 @@ static_resources:
|
|||||||
- name: envoy.filters.http.grpc_web
|
- name: envoy.filters.http.grpc_web
|
||||||
- name: envoy.filters.http.cors
|
- name: envoy.filters.http.cors
|
||||||
- name: envoy.filters.http.router
|
- name: envoy.filters.http.router
|
||||||
|
- name: haveno1_listener
|
||||||
|
address:
|
||||||
|
socket_address: { address: 0.0.0.0, port_value: 8082 }
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: auto
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: local_service
|
||||||
|
domains: ["*"]
|
||||||
|
routes:
|
||||||
|
- match: { prefix: "/" }
|
||||||
|
route:
|
||||||
|
cluster: haveno1_service
|
||||||
|
timeout: 0s
|
||||||
|
max_stream_duration:
|
||||||
|
grpc_timeout_header_max: 0s
|
||||||
|
cors:
|
||||||
|
allow_origin_string_match:
|
||||||
|
- prefix: "*"
|
||||||
|
allow_methods: GET, PUT, DELETE, POST, OPTIONS
|
||||||
|
allow_headers: password,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||||
|
max_age: "1728000"
|
||||||
|
expose_headers: custom-header-1,grpc-status,grpc-message
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.grpc_web
|
||||||
|
- name: envoy.filters.http.cors
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
- name: haveno2_listener
|
||||||
|
address:
|
||||||
|
socket_address: { address: 0.0.0.0, port_value: 8083 }
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: auto
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: local_service
|
||||||
|
domains: ["*"]
|
||||||
|
routes:
|
||||||
|
- match: { prefix: "/" }
|
||||||
|
route:
|
||||||
|
cluster: haveno2_service
|
||||||
|
timeout: 0s
|
||||||
|
max_stream_duration:
|
||||||
|
grpc_timeout_header_max: 0s
|
||||||
|
cors:
|
||||||
|
allow_origin_string_match:
|
||||||
|
- prefix: "*"
|
||||||
|
allow_methods: GET, PUT, DELETE, POST, OPTIONS
|
||||||
|
allow_headers: password,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||||
|
max_age: "1728000"
|
||||||
|
expose_headers: custom-header-1,grpc-status,grpc-message
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.grpc_web
|
||||||
|
- name: envoy.filters.http.cors
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
- name: haveno3_listener
|
||||||
|
address:
|
||||||
|
socket_address: { address: 0.0.0.0, port_value: 8084 }
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: auto
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: local_service
|
||||||
|
domains: ["*"]
|
||||||
|
routes:
|
||||||
|
- match: { prefix: "/" }
|
||||||
|
route:
|
||||||
|
cluster: haveno3_service
|
||||||
|
timeout: 0s
|
||||||
|
max_stream_duration:
|
||||||
|
grpc_timeout_header_max: 0s
|
||||||
|
cors:
|
||||||
|
allow_origin_string_match:
|
||||||
|
- prefix: "*"
|
||||||
|
allow_methods: GET, PUT, DELETE, POST, OPTIONS
|
||||||
|
allow_headers: password,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||||
|
max_age: "1728000"
|
||||||
|
expose_headers: custom-header-1,grpc-status,grpc-message
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.grpc_web
|
||||||
|
- name: envoy.filters.http.cors
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
- name: haveno4_listener
|
||||||
|
address:
|
||||||
|
socket_address: { address: 0.0.0.0, port_value: 8085 }
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: auto
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: local_service
|
||||||
|
domains: ["*"]
|
||||||
|
routes:
|
||||||
|
- match: { prefix: "/" }
|
||||||
|
route:
|
||||||
|
cluster: haveno4_service
|
||||||
|
timeout: 0s
|
||||||
|
max_stream_duration:
|
||||||
|
grpc_timeout_header_max: 0s
|
||||||
|
cors:
|
||||||
|
allow_origin_string_match:
|
||||||
|
- prefix: "*"
|
||||||
|
allow_methods: GET, PUT, DELETE, POST, OPTIONS
|
||||||
|
allow_headers: password,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||||
|
max_age: "1728000"
|
||||||
|
expose_headers: custom-header-1,grpc-status,grpc-message
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.grpc_web
|
||||||
|
- name: envoy.filters.http.cors
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
- name: haveno5_listener
|
||||||
|
address:
|
||||||
|
socket_address: { address: 0.0.0.0, port_value: 8086 }
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
codec_type: auto
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: local_service
|
||||||
|
domains: ["*"]
|
||||||
|
routes:
|
||||||
|
- match: { prefix: "/" }
|
||||||
|
route:
|
||||||
|
cluster: haveno5_service
|
||||||
|
timeout: 0s
|
||||||
|
max_stream_duration:
|
||||||
|
grpc_timeout_header_max: 0s
|
||||||
|
cors:
|
||||||
|
allow_origin_string_match:
|
||||||
|
- prefix: "*"
|
||||||
|
allow_methods: GET, PUT, DELETE, POST, OPTIONS
|
||||||
|
allow_headers: password,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||||
|
max_age: "1728000"
|
||||||
|
expose_headers: custom-header-1,grpc-status,grpc-message
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.grpc_web
|
||||||
|
- name: envoy.filters.http.cors
|
||||||
|
- name: envoy.filters.http.router
|
||||||
clusters:
|
clusters:
|
||||||
- name: alice_service
|
- name: alice_service
|
||||||
connect_timeout: 0.25s
|
connect_timeout: 0.25s
|
||||||
@ -102,3 +267,73 @@ static_resources:
|
|||||||
socket_address:
|
socket_address:
|
||||||
address: host.docker.internal
|
address: host.docker.internal
|
||||||
port_value: 10000
|
port_value: 10000
|
||||||
|
- name: haveno1_service
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: logical_dns
|
||||||
|
http2_protocol_options: {}
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: cluster_0
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: host.docker.internal
|
||||||
|
port_value: 10001
|
||||||
|
- name: haveno2_service
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: logical_dns
|
||||||
|
http2_protocol_options: {}
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: cluster_0
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: host.docker.internal
|
||||||
|
port_value: 10002
|
||||||
|
- name: haveno3_service
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: logical_dns
|
||||||
|
http2_protocol_options: {}
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: cluster_0
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: host.docker.internal
|
||||||
|
port_value: 10003
|
||||||
|
- name: haveno4_service
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: logical_dns
|
||||||
|
http2_protocol_options: {}
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: cluster_0
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: host.docker.internal
|
||||||
|
port_value: 10004
|
||||||
|
- name: haveno5_service
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: logical_dns
|
||||||
|
http2_protocol_options: {}
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: cluster_0
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: host.docker.internal
|
||||||
|
port_value: 10005
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
// import haveno types
|
// import haveno types
|
||||||
import {HavenoDaemon} from "./HavenoDaemon";
|
import {HavenoDaemon} from "./HavenoDaemon";
|
||||||
|
import {HavenoUtils} from "./HavenoUtils";
|
||||||
|
import * as grpcWeb from 'grpc-web';
|
||||||
import {XmrBalanceInfo, OfferInfo, TradeInfo, MarketPriceInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
|
import {XmrBalanceInfo, OfferInfo, TradeInfo, MarketPriceInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
|
||||||
import {PaymentAccount, Offer} from './protobuf/pb_pb';
|
import {PaymentAccount, Offer} from './protobuf/pb_pb';
|
||||||
|
|
||||||
@ -11,38 +13,45 @@ const GenUtils = monerojs.GenUtils;
|
|||||||
const MoneroTxConfig = monerojs.MoneroTxConfig;
|
const MoneroTxConfig = monerojs.MoneroTxConfig;
|
||||||
const TaskLooper = monerojs.TaskLooper;
|
const TaskLooper = monerojs.TaskLooper;
|
||||||
|
|
||||||
// import console because jest swallows messages in real time
|
// other required imports
|
||||||
const console = require('console');
|
const console = require('console'); // import console because jest swallows messages in real time
|
||||||
|
const assert = require("assert");
|
||||||
|
|
||||||
// --------------------------- TEST CONFIGURATION -----------------------------
|
// --------------------------- TEST CONFIGURATION -----------------------------
|
||||||
|
|
||||||
|
// set log level (gets more verbose increasing from 0)
|
||||||
|
HavenoUtils.setLogLevel(0);
|
||||||
|
|
||||||
|
// path to directory with haveno binaries
|
||||||
|
const HAVENO_PATH = "../haveno";
|
||||||
|
|
||||||
// wallet to fund alice and bob during tests
|
// wallet to fund alice and bob during tests
|
||||||
const fundingWalletUrl = "http://localhost:38084";
|
const FUNDING_WALLET_URL = "http://localhost:38084";
|
||||||
const fundingWalletUsername = "rpc_user";
|
const FUNDING_WALLET_USERNAME = "rpc_user";
|
||||||
const fundingWalletPassword = "abc123";
|
const FUNDING_WALLET_PASSWORD = "abc123";
|
||||||
const defaultFundingWalletPath = "test_funding_wallet";
|
const DEFAULT_FUNDING_WALLET_PATH = "test_funding_wallet";
|
||||||
const minimumFunding = BigInt("5000000000000");
|
const MINIMUM_FUNDING = BigInt("5000000000000");
|
||||||
let fundingWallet: any;
|
let fundingWallet: any;
|
||||||
|
|
||||||
// alice config
|
// alice config
|
||||||
const havenoVersion = "1.6.2";
|
const HAVENO_VERSION = "1.6.2";
|
||||||
const aliceDaemonUrl = "http://localhost:8080";
|
const ALICE_DAEMON_URL = "http://localhost:8080";
|
||||||
const aliceDaemonPassword = "apitest";
|
const ALICE_DAEMON_PASSWORD = "apitest";
|
||||||
const alice: HavenoDaemon = new HavenoDaemon(aliceDaemonUrl, aliceDaemonPassword);
|
const ALICE_WALLET_URL = "http://127.0.0.1:38091"; // alice's internal haveno wallet for direct testing
|
||||||
const aliceWalletUrl = "http://127.0.0.1:38091"; // alice's internal haveno wallet for direct testing
|
const ALICE_WALLET_USERNAME = "rpc_user";
|
||||||
const aliceWalletUsername = "rpc_user";
|
const ALICE_WALLET_PASSWORD = "abc123";
|
||||||
const aliceWalletPassword = "abc123";
|
let alice: HavenoDaemon;
|
||||||
let aliceWallet: any;
|
let aliceWallet: any;
|
||||||
|
|
||||||
// bob config
|
// bob config
|
||||||
const bobDaemonUrl = "http://localhost:8081";
|
const BOB_DAEMON_URL = "http://localhost:8081";
|
||||||
const bobDaemonPassword = "apitest";
|
const BOB_DAEMON_PASSWORD = "apitest";
|
||||||
const bob: HavenoDaemon = new HavenoDaemon(bobDaemonUrl, bobDaemonPassword);
|
let bob: HavenoDaemon = new HavenoDaemon(BOB_DAEMON_URL, BOB_DAEMON_PASSWORD);
|
||||||
|
|
||||||
// monero daemon config
|
// monero daemon config
|
||||||
const moneroDaemonUrl = "http://localhost:38081"
|
const MONERO_DAEMON_URL = "http://localhost:38081"
|
||||||
const moneroDaemonUsername = "superuser";
|
const MONERO_DAEMON_USERNAME = "superuser";
|
||||||
const moneroDaemonPassword = "abctesting123";
|
const MONERO_DAEMON_PASSWORD = "abctesting123";
|
||||||
let monerod: any;
|
let monerod: any;
|
||||||
|
|
||||||
// other test config
|
// other test config
|
||||||
@ -60,15 +69,29 @@ const TEST_CRYPTO_ACCOUNTS = [ // TODO (woodser): test other cryptos, fiat
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// map proxied ports to havenod api and p2p ports
|
||||||
|
const PROXY_PORTS = new Map<string, string[]>([
|
||||||
|
["8080", ["9999", "5555"]],
|
||||||
|
["8081", ["10000", "6666"]],
|
||||||
|
["8082", ["10001", "7777"]],
|
||||||
|
["8083", ["10002", "7778"]],
|
||||||
|
["8084", ["10003", "7779"]],
|
||||||
|
["8085", ["10004", "7780"]],
|
||||||
|
["8086", ["10005", "7781"]],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// track started haveno processes
|
||||||
|
const HAVENO_PROCESSES: HavenoDaemon[] = [];
|
||||||
|
|
||||||
// ----------------------------------- TESTS ----------------------------------
|
// ----------------------------------- TESTS ----------------------------------
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|
||||||
// initialize client of monerod
|
// initialize clients
|
||||||
monerod = await monerojs.connectToDaemonRpc(moneroDaemonUrl, moneroDaemonUsername, moneroDaemonPassword);
|
alice = new HavenoDaemon(ALICE_DAEMON_URL, ALICE_DAEMON_PASSWORD);
|
||||||
|
bob = new HavenoDaemon(BOB_DAEMON_URL, BOB_DAEMON_PASSWORD);
|
||||||
// create client connected to alice's internal wallet
|
monerod = await monerojs.connectToDaemonRpc(MONERO_DAEMON_URL, MONERO_DAEMON_USERNAME, MONERO_DAEMON_PASSWORD);
|
||||||
aliceWallet = await monerojs.connectToWalletRpc(aliceWalletUrl, aliceWalletUsername, aliceWalletPassword);
|
aliceWallet = await monerojs.connectToWalletRpc(ALICE_WALLET_URL, ALICE_WALLET_USERNAME, ALICE_WALLET_PASSWORD);
|
||||||
|
|
||||||
// initialize funding wallet
|
// initialize funding wallet
|
||||||
await initFundingWallet();
|
await initFundingWallet();
|
||||||
@ -83,7 +106,7 @@ beforeAll(async () => {
|
|||||||
jest.setTimeout(300000);
|
jest.setTimeout(300000);
|
||||||
test("Can get the version", async () => {
|
test("Can get the version", async () => {
|
||||||
let version = await alice.getVersion();
|
let version = await alice.getVersion();
|
||||||
expect(version).toEqual(havenoVersion);
|
expect(version).toEqual(HAVENO_VERSION);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Can get market prices", async () => {
|
test("Can get market prices", async () => {
|
||||||
@ -201,7 +224,7 @@ test("Can post and remove an offer", async () => {
|
|||||||
let unlockedBalanceBefore: bigint = BigInt((await alice.getBalances()).getUnlockedBalance());
|
let unlockedBalanceBefore: bigint = BigInt((await alice.getBalances()).getUnlockedBalance());
|
||||||
|
|
||||||
// post offer
|
// post offer
|
||||||
let offer: OfferInfo = await postOffer(alice, "buy", BigInt("200000000000"));
|
let offer: OfferInfo = await postOffer(alice, "buy", BigInt("200000000000"), undefined);
|
||||||
|
|
||||||
// cancel offer
|
// cancel offer
|
||||||
await alice.removeOffer(offer.getId());
|
await alice.removeOffer(offer.getId());
|
||||||
@ -225,7 +248,7 @@ test("Invalidates offers when reserved funds are spent", async () => {
|
|||||||
|
|
||||||
// post offer
|
// post offer
|
||||||
await wait(1000);
|
await wait(1000);
|
||||||
let offer: OfferInfo = await postOffer(alice, "buy", tradeAmount);
|
let offer: OfferInfo = await postOffer(alice, "buy", tradeAmount, undefined);
|
||||||
|
|
||||||
// get key images reserved by offer
|
// get key images reserved by offer
|
||||||
let reservedKeyImages = [];
|
let reservedKeyImages = [];
|
||||||
@ -266,6 +289,70 @@ test("Invalidates offers when reserved funds are spent", async () => {
|
|||||||
await monerod.flushTxPool(tx.getHash());
|
await monerod.flushTxPool(tx.getHash());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Cannot make or take offer with insufficient unlocked funds", async () => {
|
||||||
|
let charlie: HavenoDaemon | undefined;
|
||||||
|
let err: any;
|
||||||
|
try {
|
||||||
|
|
||||||
|
// start charlie
|
||||||
|
charlie = await startTraderProcess();
|
||||||
|
|
||||||
|
// charlie creates ethereum payment account
|
||||||
|
let testAccount = TEST_CRYPTO_ACCOUNTS[0];
|
||||||
|
let ethPaymentAccount: PaymentAccount = await charlie.createCryptoPaymentAccount(
|
||||||
|
testAccount.currencyCode + " " + testAccount.address.substr(0, 8) + "... " + GenUtils.getUUID(),
|
||||||
|
testAccount.currencyCode,
|
||||||
|
testAccount.address);
|
||||||
|
|
||||||
|
// charlie cannot make offer with insufficient funds
|
||||||
|
try {
|
||||||
|
await postOffer(charlie, "buy", BigInt("200000000000"), ethPaymentAccount.getId());
|
||||||
|
throw new Error("Should have failed making offer with insufficient funds")
|
||||||
|
} catch (err) {
|
||||||
|
let errTyped = err as grpcWeb.RpcError;
|
||||||
|
assert.equal(errTyped.code, 2);
|
||||||
|
assert(errTyped.message.includes("not enough money"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// alice posts offer
|
||||||
|
let offers: OfferInfo[] = await alice.getMyOffers("buy"); // TODO: support alice.getMyOffers() without direction
|
||||||
|
let offer: OfferInfo;
|
||||||
|
if (offers.length) offer = offers[0];
|
||||||
|
else {
|
||||||
|
let tradeAmount: bigint = BigInt("250000000000");
|
||||||
|
await waitForUnlockedBalance(tradeAmount * BigInt("2"), alice);
|
||||||
|
offer = await postOffer(alice, "buy", tradeAmount, undefined);
|
||||||
|
assert.equal(offer.getState(), "AVAILABLE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// charlie cannot take offer with insufficient funds
|
||||||
|
try {
|
||||||
|
await charlie.takeOffer(offer.getId(), ethPaymentAccount.getId()); // TODO (woodser): this returns before trade is fully initialized. this fails with bad error message if trade is not yet seen by peer
|
||||||
|
throw new Error("Should have failed taking offer with insufficient funds")
|
||||||
|
} catch (err) {
|
||||||
|
let errTyped = err as grpcWeb.RpcError;
|
||||||
|
assert.equal(errTyped.code, 2);
|
||||||
|
assert(errTyped.message.includes("not enough money")); // TODO (woodser): error message does not contain stacktrace
|
||||||
|
}
|
||||||
|
|
||||||
|
// charlie does not have trade
|
||||||
|
try {
|
||||||
|
await charlie.getTrade(offer.getId());
|
||||||
|
} catch (err) {
|
||||||
|
let errTyped = err as grpcWeb.RpcError;
|
||||||
|
assert.equal(errTyped.code, 3);
|
||||||
|
assert(errTyped.message.includes("trade with id '" + offer.getId() + "' not found")); // TODO (woodser): error message does not contain stacktrace
|
||||||
|
}
|
||||||
|
} catch (err2) {
|
||||||
|
err = err2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop charlie
|
||||||
|
if (charlie) await stopHavenoProcess(charlie);
|
||||||
|
// TODO: how to delete trader app folder at end of test?
|
||||||
|
if (err) throw err;
|
||||||
|
});
|
||||||
|
|
||||||
// TODO (woodser): test grpc notifications
|
// TODO (woodser): test grpc notifications
|
||||||
test("Can complete a trade", async () => {
|
test("Can complete a trade", async () => {
|
||||||
|
|
||||||
@ -278,7 +365,7 @@ test("Can complete a trade", async () => {
|
|||||||
// alice posts offer to buy xmr
|
// alice posts offer to buy xmr
|
||||||
console.log("Alice posting offer");
|
console.log("Alice posting offer");
|
||||||
let direction = "buy";
|
let direction = "buy";
|
||||||
let offer: OfferInfo = await postOffer(alice, direction, tradeAmount);
|
let offer: OfferInfo = await postOffer(alice, direction, tradeAmount, undefined);
|
||||||
expect(offer.getState()).toEqual("AVAILABLE");
|
expect(offer.getState()).toEqual("AVAILABLE");
|
||||||
console.log("Alice done posting offer");
|
console.log("Alice done posting offer");
|
||||||
|
|
||||||
@ -368,13 +455,60 @@ test("Can complete a trade", async () => {
|
|||||||
|
|
||||||
// ------------------------------- HELPERS ------------------------------------
|
// ------------------------------- HELPERS ------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a Haveno trader process.
|
||||||
|
*
|
||||||
|
* @return {HavenoDaemon} the client connected to the started Haveno process
|
||||||
|
*/
|
||||||
|
async function startTraderProcess(): Promise<HavenoDaemon> {
|
||||||
|
|
||||||
|
// iterate to find unused proxy port
|
||||||
|
for (let port of Array.from(PROXY_PORTS.keys())) {
|
||||||
|
if (port === "8080" || port === "8081") continue; // reserved for alice and bob
|
||||||
|
let used = false;
|
||||||
|
for (let havenod of HAVENO_PROCESSES) {
|
||||||
|
if (port === new URL(havenod.getUrl()).port) {
|
||||||
|
used = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start haveno process on unused port
|
||||||
|
if (!used) {
|
||||||
|
let appName = "haveno-XMR_STAGENET_trader_" + GenUtils.getUUID();
|
||||||
|
let cmd: string[] = [
|
||||||
|
"./haveno-daemon",
|
||||||
|
"--baseCurrencyNetwork", "XMR_STAGENET",
|
||||||
|
"--useLocalhostForP2P", "true",
|
||||||
|
"--useDevPrivilegeKeys", "true",
|
||||||
|
"--nodePort", PROXY_PORTS.get(port)![1],
|
||||||
|
"--appName", appName,
|
||||||
|
"--apiPassword", "apitest",
|
||||||
|
"--apiPort", PROXY_PORTS.get(port)![0]
|
||||||
|
];
|
||||||
|
let havenod = await HavenoDaemon.startProcess(HAVENO_PATH, cmd, "http://localhost:" + port);
|
||||||
|
HAVENO_PROCESSES.push(havenod);
|
||||||
|
return havenod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("No unused test ports available");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop a Haveno trader process and release its ports for reuse.
|
||||||
|
*/
|
||||||
|
async function stopHavenoProcess(havenod: HavenoDaemon) {
|
||||||
|
await havenod.stopProcess();
|
||||||
|
GenUtils.remove(HAVENO_PROCESSES, havenod);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open or create funding wallet.
|
* Open or create funding wallet.
|
||||||
*/
|
*/
|
||||||
async function initFundingWallet() {
|
async function initFundingWallet() {
|
||||||
|
|
||||||
// init client connected to monero-wallet-rpc
|
// init client connected to monero-wallet-rpc
|
||||||
fundingWallet = await monerojs.connectToWalletRpc(fundingWalletUrl, fundingWalletUsername, fundingWalletPassword);
|
fundingWallet = await monerojs.connectToWalletRpc(FUNDING_WALLET_URL, FUNDING_WALLET_USERNAME, FUNDING_WALLET_PASSWORD);
|
||||||
|
|
||||||
// check if wallet is open
|
// check if wallet is open
|
||||||
let walletIsOpen = false
|
let walletIsOpen = false
|
||||||
@ -388,7 +522,7 @@ async function initFundingWallet() {
|
|||||||
|
|
||||||
// attempt to open funding wallet
|
// attempt to open funding wallet
|
||||||
try {
|
try {
|
||||||
await fundingWallet.openWallet({path: defaultFundingWalletPath, password: fundingWalletPassword});
|
await fundingWallet.openWallet({path: DEFAULT_FUNDING_WALLET_PATH, password: FUNDING_WALLET_PASSWORD});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!(e instanceof monerojs.MoneroRpcError)) throw e;
|
if (!(e instanceof monerojs.MoneroRpcError)) throw e;
|
||||||
|
|
||||||
@ -396,7 +530,7 @@ async function initFundingWallet() {
|
|||||||
if (e.getCode() === -1) {
|
if (e.getCode() === -1) {
|
||||||
|
|
||||||
// create wallet
|
// create wallet
|
||||||
await fundingWallet.createWallet({path: defaultFundingWalletPath, password: fundingWalletPassword});
|
await fundingWallet.createWallet({path: DEFAULT_FUNDING_WALLET_PATH, password: FUNDING_WALLET_PASSWORD});
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -447,7 +581,7 @@ async function waitForUnlockedBalance(amount: bigint, ...wallets: any[]) {
|
|||||||
if (depositNeeded > BigInt("0") && wallet._wallet !== fundingWallet) fundConfig.addDestination(await wallet.getDepositAddress(), depositNeeded * BigInt("10")); // deposit 10 times more than needed
|
if (depositNeeded > BigInt("0") && wallet._wallet !== fundingWallet) fundConfig.addDestination(await wallet.getDepositAddress(), depositNeeded * BigInt("10")); // deposit 10 times more than needed
|
||||||
}
|
}
|
||||||
if (fundConfig.getDestinations()) {
|
if (fundConfig.getDestinations()) {
|
||||||
await waitForUnlockedBalance(minimumFunding, fundingWallet); // TODO (woodser): wait for enough to cover tx amount + fee
|
await waitForUnlockedBalance(MINIMUM_FUNDING, fundingWallet); // TODO (woodser): wait for enough to cover tx amount + fee
|
||||||
try { await fundingWallet.createTx(fundConfig); }
|
try { await fundingWallet.createTx(fundConfig); }
|
||||||
catch (err) { throw new Error("Error funding wallets: " + err.message); }
|
catch (err) { throw new Error("Error funding wallets: " + err.message); }
|
||||||
}
|
}
|
||||||
@ -506,14 +640,17 @@ async function wait(durationMs: number) {
|
|||||||
return new Promise(function(resolve) { setTimeout(resolve, durationMs); });
|
return new Promise(function(resolve) { setTimeout(resolve, durationMs); });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postOffer(maker: HavenoDaemon, direction: string, amount: bigint) {
|
async function postOffer(maker: HavenoDaemon, direction: string, amount: bigint, paymentAccountId: string|undefined) {
|
||||||
|
|
||||||
// maker creates ethereum payment account
|
// create payment account if not given
|
||||||
let testAccount = TEST_CRYPTO_ACCOUNTS[0];
|
if (!paymentAccountId) {
|
||||||
let ethPaymentAccount: PaymentAccount = await maker.createCryptoPaymentAccount(
|
let testAccount = TEST_CRYPTO_ACCOUNTS[0];
|
||||||
testAccount.currencyCode + " " + testAccount.address.substr(0, 8) + "... " + GenUtils.getUUID(),
|
let ethPaymentAccount: PaymentAccount = await maker.createCryptoPaymentAccount(
|
||||||
testAccount.currencyCode,
|
testAccount.currencyCode + " " + testAccount.address.substr(0, 8) + "... " + GenUtils.getUUID(),
|
||||||
testAccount.address);
|
testAccount.currencyCode,
|
||||||
|
testAccount.address);
|
||||||
|
paymentAccountId = ethPaymentAccount.getId();
|
||||||
|
}
|
||||||
|
|
||||||
// get unlocked balance before reserving offer
|
// get unlocked balance before reserving offer
|
||||||
let unlockedBalanceBefore: bigint = BigInt((await maker.getBalances()).getUnlockedBalance());
|
let unlockedBalanceBefore: bigint = BigInt((await maker.getBalances()).getUnlockedBalance());
|
||||||
@ -527,7 +664,7 @@ async function postOffer(maker: HavenoDaemon, direction: string, amount: bigint)
|
|||||||
amount, // amount
|
amount, // amount
|
||||||
BigInt("150000000000"), // min amount
|
BigInt("150000000000"), // min amount
|
||||||
0.15, // buyer security deposit, e.g. 15%
|
0.15, // buyer security deposit, e.g. 15%
|
||||||
ethPaymentAccount.getId(), // payment account id
|
paymentAccountId, // payment account id
|
||||||
undefined); // trigger price // TODO: fails if there is a decimal, gets converted to long?
|
undefined); // trigger price // TODO: fails if there is a decimal, gets converted to long?
|
||||||
testOffer(offer);
|
testOffer(offer);
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import {HavenoUtils} from "./HavenoUtils";
|
||||||
import * as grpcWeb from 'grpc-web';
|
import * as grpcWeb from 'grpc-web';
|
||||||
import {GetVersionClient, PriceClient, WalletsClient, OffersClient, PaymentAccountsClient, TradesClient} from './protobuf/GrpcServiceClientPb';
|
import {GetVersionClient, PriceClient, WalletsClient, OffersClient, PaymentAccountsClient, TradesClient} from './protobuf/GrpcServiceClientPb';
|
||||||
import {GetVersionRequest, GetVersionReply, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest} from './protobuf/grpc_pb';
|
import {GetVersionRequest, GetVersionReply, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest} from './protobuf/grpc_pb';
|
||||||
import {PaymentAccount, AvailabilityResult} from './protobuf/pb_pb';
|
import {PaymentAccount, AvailabilityResult} from './protobuf/pb_pb';
|
||||||
|
const console = require('console');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Haveno daemon client using gRPC.
|
* Haveno daemon client using gRPC.
|
||||||
@ -11,6 +13,7 @@ class HavenoDaemon {
|
|||||||
// instance variables
|
// instance variables
|
||||||
_url: string;
|
_url: string;
|
||||||
_password: string;
|
_password: string;
|
||||||
|
_process: any;
|
||||||
_getVersionClient: GetVersionClient;
|
_getVersionClient: GetVersionClient;
|
||||||
_priceClient: PriceClient;
|
_priceClient: PriceClient;
|
||||||
_walletsClient: WalletsClient;
|
_walletsClient: WalletsClient;
|
||||||
@ -22,9 +25,12 @@ class HavenoDaemon {
|
|||||||
* Construct a client connected to a Haveno daemon.
|
* Construct a client connected to a Haveno daemon.
|
||||||
*
|
*
|
||||||
* @param {string} url - Haveno daemon url
|
* @param {string} url - Haveno daemon url
|
||||||
* @param {string} password - Haveno daemon password if applicable
|
* @param {string} password - Haveno daemon password
|
||||||
*/
|
*/
|
||||||
constructor(url: string, password: string) {
|
constructor(url: string, password: string) {
|
||||||
|
HavenoUtils.log(1, "Creating HavenoDaemon(" + url + ", " + password + ")");
|
||||||
|
if (!url) throw new Error("Must provide URL of Haveno daemon");
|
||||||
|
if (!password) throw new Error("Must provide password of Haveno daemon");
|
||||||
this._url = url;
|
this._url = url;
|
||||||
this._password = password;
|
this._password = password;
|
||||||
this._getVersionClient = new GetVersionClient(this._url);
|
this._getVersionClient = new GetVersionClient(this._url);
|
||||||
@ -35,6 +41,101 @@ class HavenoDaemon {
|
|||||||
this._tradesClient = new TradesClient(this._url);
|
this._tradesClient = new TradesClient(this._url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new Haveno process.
|
||||||
|
*
|
||||||
|
* @param {string} havenoPath - path to Haveno binaries
|
||||||
|
* @param {string[]} cmd - command to start the process
|
||||||
|
* @param {string} url - Haveno daemon url (must proxy to api port)
|
||||||
|
* @return {HavenoDaemon} a client connected to the newly started Haveno process
|
||||||
|
*/
|
||||||
|
static async startProcess(havenoPath: string, cmd: string[], url: string): Promise<HavenoDaemon> {
|
||||||
|
HavenoUtils.log(1, "Starting Haveno process: " + cmd);
|
||||||
|
|
||||||
|
// start process
|
||||||
|
let process = require('child_process').spawn(cmd[0], cmd.slice(1), {cwd: havenoPath});
|
||||||
|
process.stdout.setEncoding('utf8');
|
||||||
|
process.stderr.setEncoding('utf8');
|
||||||
|
|
||||||
|
// return promise which resolves after starting havenod
|
||||||
|
let output = "";
|
||||||
|
let isResolved = false;
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
|
||||||
|
// handle stdout
|
||||||
|
process.stdout.on('data', async function(data: any) {
|
||||||
|
let line = data.toString();
|
||||||
|
HavenoUtils.log(2, line);
|
||||||
|
output += line + '\n'; // capture output in case of error
|
||||||
|
|
||||||
|
// read success message
|
||||||
|
if (line.indexOf("initDomainServices") >= 0) {
|
||||||
|
|
||||||
|
// get api password
|
||||||
|
let passwordIdx = cmd.indexOf("--apiPassword");
|
||||||
|
if (passwordIdx < 0) {
|
||||||
|
reject("Must provide API password to start Haveno daemon");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let password = cmd[passwordIdx + 1];
|
||||||
|
|
||||||
|
// create client connected to internal process
|
||||||
|
let daemon = new HavenoDaemon(url, password);
|
||||||
|
daemon._process = process;
|
||||||
|
|
||||||
|
// resolve promise with client connected to internal process
|
||||||
|
isResolved = true;
|
||||||
|
resolve(daemon);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle stderr
|
||||||
|
process.stderr.on('data', function(data: any) {
|
||||||
|
if (HavenoUtils.getLogLevel() >= 2) console.error(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle exit
|
||||||
|
process.on("exit", function(code: any) {
|
||||||
|
if (!isResolved) reject(new Error("Haveno process terminated with exit code " + code + (output ? ":\n\n" + output : "")));
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle error
|
||||||
|
process.on("error", function(err: any) {
|
||||||
|
if (err.message.indexOf("ENOENT") >= 0) reject(new Error("haveno-daemon does not exist at path '" + cmd[0] + "'"));
|
||||||
|
if (!isResolved) reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle uncaught exception
|
||||||
|
process.on("uncaughtException", function(err: any, origin: any) {
|
||||||
|
console.error("Uncaught exception in Haveno process: " + err.message);
|
||||||
|
console.error(origin);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop a previously started Haveno process.
|
||||||
|
*/
|
||||||
|
async stopProcess(): Promise<void> {
|
||||||
|
if (this._process === undefined) throw new Error("HavenoDaemon instance not created from new process");
|
||||||
|
let that = this;
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
that._process.on("exit", function() { resolve(); });
|
||||||
|
that._process.on("error", function(err: any) { reject(err); });
|
||||||
|
that._process.kill("SIGINT");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL of the Haveno daemon.
|
||||||
|
*
|
||||||
|
* @return {string} the URL of the Haveno daemon
|
||||||
|
*/
|
||||||
|
getUrl(): string {
|
||||||
|
return this._url;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Haveno version.
|
* Get the Haveno version.
|
||||||
*
|
*
|
||||||
@ -198,7 +299,7 @@ class HavenoDaemon {
|
|||||||
* @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount
|
* @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount
|
||||||
* @param {string} paymentAccountId - payment account id
|
* @param {string} paymentAccountId - payment account id
|
||||||
* @param {number} triggerPrice - price to remove offer
|
* @param {number} triggerPrice - price to remove offer
|
||||||
* @return {OfferInfo} the created offer
|
* @return {OfferInfo} the posted offer
|
||||||
*/
|
*/
|
||||||
async postOffer(currencyCode: string,
|
async postOffer(currencyCode: string,
|
||||||
direction: string,
|
direction: string,
|
||||||
|
42
src/HavenoUtils.ts
Normal file
42
src/HavenoUtils.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const assert = require("assert");
|
||||||
|
const console = require('console');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of utilities for working with Haveno.
|
||||||
|
*/
|
||||||
|
class HavenoUtils {
|
||||||
|
|
||||||
|
static LOG_LEVEL = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a message.
|
||||||
|
*
|
||||||
|
* @param {int} level - log level of the message
|
||||||
|
* @param {string} msg - message to log
|
||||||
|
*/
|
||||||
|
static log(level: number, msg: string) {
|
||||||
|
assert(level === parseInt(level + "", 10) && level >= 0, "Log level must be an integer >= 0");
|
||||||
|
if (HavenoUtils.LOG_LEVEL >= level) process.stdout.write(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the log level with 0 being least verbose.
|
||||||
|
*
|
||||||
|
* @param {int} level - the log level
|
||||||
|
*/
|
||||||
|
static async setLogLevel(level: number) {
|
||||||
|
assert(level === parseInt(level + "", 10) && level >= 0, "Log level must be an integer >= 0");
|
||||||
|
HavenoUtils.LOG_LEVEL = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the log level.
|
||||||
|
*
|
||||||
|
* @return {int} the current log level
|
||||||
|
*/
|
||||||
|
static getLogLevel(): number {
|
||||||
|
return HavenoUtils.LOG_LEVEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {HavenoUtils};
|
Loading…
x
Reference in New Issue
Block a user