integration testing tweaks, rerun-payment-multiple scenario
This commit is contained in:
parent
4525942777
commit
3321e40bff
@ -46,6 +46,7 @@ import {
|
||||
PostOrderRequest,
|
||||
PostOrderResponse,
|
||||
} from "./merchantApiTypes";
|
||||
import { EddsaKeyPair } from "taler-wallet-core/lib/crypto/talerCrypto";
|
||||
|
||||
const exec = util.promisify(require("child_process").exec);
|
||||
|
||||
@ -77,7 +78,6 @@ export async function sh(
|
||||
shell: true,
|
||||
});
|
||||
proc.stdout.on("data", (x) => {
|
||||
console.log("child process got data chunk");
|
||||
if (x instanceof Buffer) {
|
||||
stdoutChunks.push(x);
|
||||
} else {
|
||||
@ -363,8 +363,6 @@ export interface BankConfig {
|
||||
currency: string;
|
||||
httpPort: number;
|
||||
database: string;
|
||||
suggestedExchange: string | undefined;
|
||||
suggestedExchangePayto: string | undefined;
|
||||
allowRegistrations: boolean;
|
||||
}
|
||||
|
||||
@ -397,8 +395,48 @@ function setCoin(config: Configuration, c: CoinConfig) {
|
||||
config.setString(s, "rsa_keysize", `${c.rsaKeySize}`);
|
||||
}
|
||||
|
||||
async function pingProc(
|
||||
proc: ProcessWrapper | undefined,
|
||||
url: string,
|
||||
serviceName: string,
|
||||
): Promise<void> {
|
||||
if (!proc || proc.proc.exitCode !== null) {
|
||||
throw Error(`service process ${serviceName} not started, can't ping`);
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
console.log(`pinging ${serviceName}`);
|
||||
const resp = await axios.get(url);
|
||||
console.log(`service ${serviceName} available`);
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log(`service ${serviceName} not ready:`, e.toString());
|
||||
await delay(1000);
|
||||
}
|
||||
if (!proc || proc.proc.exitCode !== null) {
|
||||
throw Error(`service process ${serviceName} stopped unexpectedly`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class BankService {
|
||||
proc: ProcessWrapper | undefined;
|
||||
|
||||
static fromExistingConfig(gc: GlobalTestState): BankService {
|
||||
const cfgFilename = gc.testDir + "/bank.conf";
|
||||
console.log("reading bank config from", cfgFilename);
|
||||
const config = Configuration.load(cfgFilename);
|
||||
const bc: BankConfig = {
|
||||
allowRegistrations: config
|
||||
.getYesNo("bank", "allow_registrations")
|
||||
.required(),
|
||||
currency: config.getString("taler", "currency").required(),
|
||||
database: config.getString("bank", "database").required(),
|
||||
httpPort: config.getNumber("bank", "http_port").required(),
|
||||
};
|
||||
return new BankService(gc, bc, cfgFilename);
|
||||
}
|
||||
|
||||
static async create(
|
||||
gc: GlobalTestState,
|
||||
bc: BankConfig,
|
||||
@ -414,21 +452,17 @@ export class BankService {
|
||||
"allow_registrations",
|
||||
bc.allowRegistrations ? "yes" : "no",
|
||||
);
|
||||
if (bc.suggestedExchange) {
|
||||
config.setString("bank", "suggested_exchange", bc.suggestedExchange);
|
||||
}
|
||||
if (bc.suggestedExchangePayto) {
|
||||
config.setString(
|
||||
"bank",
|
||||
"suggested_exchange_payto",
|
||||
bc.suggestedExchangePayto,
|
||||
);
|
||||
}
|
||||
const cfgFilename = gc.testDir + "/bank.conf";
|
||||
config.write(cfgFilename);
|
||||
return new BankService(gc, bc, cfgFilename);
|
||||
}
|
||||
|
||||
setSuggestedExchange(e: ExchangeService, exchangePayto: string) {
|
||||
const config = Configuration.load(this.configFile);
|
||||
config.setString("bank", "suggested_exchange", e.baseUrl);
|
||||
config.setString("bank", "suggested_exchange_payto", exchangePayto);
|
||||
}
|
||||
|
||||
get port() {
|
||||
return this.bankConfig.httpPort;
|
||||
}
|
||||
@ -449,16 +483,7 @@ export class BankService {
|
||||
|
||||
async pingUntilAvailable(): Promise<void> {
|
||||
const url = `http://localhost:${this.bankConfig.httpPort}/config`;
|
||||
while (true) {
|
||||
try {
|
||||
console.log("pinging bank");
|
||||
const resp = await axios.get(url);
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("bank not ready:", e.toString());
|
||||
await delay(1000);
|
||||
}
|
||||
}
|
||||
await pingProc(this.proc, url, "bank");
|
||||
}
|
||||
|
||||
async createAccount(username: string, password: string): Promise<void> {
|
||||
@ -546,7 +571,6 @@ export interface ExchangeConfig {
|
||||
roundUnit?: string;
|
||||
httpPort: number;
|
||||
database: string;
|
||||
coinConfig?: ((curr: string) => CoinConfig)[];
|
||||
}
|
||||
|
||||
export interface ExchangeServiceInterface {
|
||||
@ -557,6 +581,27 @@ export interface ExchangeServiceInterface {
|
||||
}
|
||||
|
||||
export class ExchangeService implements ExchangeServiceInterface {
|
||||
static fromExistingConfig(gc: GlobalTestState, exchangeName: string) {
|
||||
const cfgFilename = gc.testDir + `/exchange-${exchangeName}.conf`;
|
||||
const config = Configuration.load(cfgFilename);
|
||||
const ec: ExchangeConfig = {
|
||||
currency: config.getString("taler", "currency").required(),
|
||||
database: config.getString("exchangedb-postgres", "config").required(),
|
||||
httpPort: config.getNumber("exchange", "port").required(),
|
||||
name: exchangeName,
|
||||
roundUnit: config.getString("taler", "currency_round_unit").required(),
|
||||
};
|
||||
const privFile = config
|
||||
.getPath("exchange", "master_priv_file")
|
||||
.required();
|
||||
const eddsaPriv = fs.readFileSync(privFile);
|
||||
const keyPair: EddsaKeyPair = {
|
||||
eddsaPriv,
|
||||
eddsaPub: talerCrypto.eddsaGetPublic(eddsaPriv),
|
||||
};
|
||||
return new ExchangeService(gc, ec, cfgFilename, keyPair);
|
||||
}
|
||||
|
||||
static create(gc: GlobalTestState, e: ExchangeConfig) {
|
||||
const config = new Configuration();
|
||||
config.setString("taler", "currency", e.currency);
|
||||
@ -586,7 +631,6 @@ export class ExchangeService implements ExchangeServiceInterface {
|
||||
);
|
||||
config.setString("exchange", "serve", "tcp");
|
||||
config.setString("exchange", "port", `${e.httpPort}`);
|
||||
config.setString("exchange", "port", `${e.httpPort}`);
|
||||
config.setString("exchange", "signkey_duration", "4 weeks");
|
||||
config.setString("exchange", "legal_duraction", "2 years");
|
||||
config.setString("exchange", "lookahead_sign", "32 weeks 1 day");
|
||||
@ -607,10 +651,6 @@ export class ExchangeService implements ExchangeServiceInterface {
|
||||
|
||||
config.setString("exchangedb-postgres", "config", e.database);
|
||||
|
||||
const coinConfig = e.coinConfig ?? defaultCoinConfig;
|
||||
|
||||
coinConfig.forEach((cc) => setCoin(config, cc(e.currency)));
|
||||
|
||||
const exchangeMasterKey = talerCrypto.createEddsaKeyPair();
|
||||
|
||||
config.setString(
|
||||
@ -632,6 +672,14 @@ export class ExchangeService implements ExchangeServiceInterface {
|
||||
return new ExchangeService(gc, e, cfgFilename, exchangeMasterKey);
|
||||
}
|
||||
|
||||
addOfferedCoins(offeredCoins: ((curr: string) => CoinConfig)[]) {
|
||||
const config = Configuration.load(this.configFilename);
|
||||
offeredCoins.forEach((cc) =>
|
||||
setCoin(config, cc(this.exchangeConfig.currency)),
|
||||
);
|
||||
config.write(this.configFilename);
|
||||
}
|
||||
|
||||
get masterPub() {
|
||||
return talerCrypto.encodeCrock(this.keyPair.eddsaPub);
|
||||
}
|
||||
@ -713,16 +761,7 @@ export class ExchangeService implements ExchangeServiceInterface {
|
||||
|
||||
async pingUntilAvailable(): Promise<void> {
|
||||
const url = `http://localhost:${this.exchangeConfig.httpPort}/keys`;
|
||||
while (true) {
|
||||
try {
|
||||
console.log("pinging exchange");
|
||||
const resp = await axios.get(url);
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("exchange not ready:", e.toString());
|
||||
await delay(1000);
|
||||
}
|
||||
}
|
||||
await pingProc(this.exchangeHttpProc, url, `exchange (${this.name})`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -734,6 +773,18 @@ export interface MerchantConfig {
|
||||
}
|
||||
|
||||
export class MerchantService {
|
||||
static fromExistingConfig(gc: GlobalTestState, name: string) {
|
||||
const cfgFilename = gc.testDir + `/merchant-${name}.conf`;
|
||||
const config = Configuration.load(cfgFilename);
|
||||
const mc: MerchantConfig = {
|
||||
currency: config.getString("taler", "currency").required(),
|
||||
database: config.getString("merchantdb-postgres", "config").required(),
|
||||
httpPort: config.getNumber("merchant", "port").required(),
|
||||
name,
|
||||
};
|
||||
return new MerchantService(gc, mc, cfgFilename);
|
||||
}
|
||||
|
||||
proc: ProcessWrapper | undefined;
|
||||
|
||||
constructor(
|
||||
@ -844,16 +895,7 @@ export class MerchantService {
|
||||
|
||||
async pingUntilAvailable(): Promise<void> {
|
||||
const url = `http://localhost:${this.merchantConfig.httpPort}/config`;
|
||||
while (true) {
|
||||
try {
|
||||
console.log("pinging merchant");
|
||||
const resp = await axios.get(url);
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("merchant not ready", e.toString());
|
||||
await delay(1000);
|
||||
}
|
||||
}
|
||||
await pingProc(this.proc, url, `merchant (${this.merchantConfig.name})`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -903,16 +945,13 @@ function updateCurrentSymlink(testDir: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function runTest(testMain: (gc: GlobalTestState) => Promise<void>) {
|
||||
export function runTestWithState(
|
||||
gc: GlobalTestState,
|
||||
testMain: (t: GlobalTestState) => Promise<void>,
|
||||
) {
|
||||
const main = async () => {
|
||||
let gc: GlobalTestState | undefined;
|
||||
let ret = 0;
|
||||
try {
|
||||
gc = new GlobalTestState({
|
||||
testDir: fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), "taler-integrationtest-"),
|
||||
),
|
||||
});
|
||||
updateCurrentSymlink(gc.testDir);
|
||||
console.log("running test in directory", gc.testDir);
|
||||
await testMain(gc);
|
||||
@ -936,6 +975,15 @@ export function runTest(testMain: (gc: GlobalTestState) => Promise<void>) {
|
||||
main();
|
||||
}
|
||||
|
||||
export function runTest(
|
||||
testMain: (gc: GlobalTestState) => Promise<void>,
|
||||
): void {
|
||||
const gc = new GlobalTestState({
|
||||
testDir: fs.mkdtempSync(path.join(os.tmpdir(), "taler-integrationtest-")),
|
||||
});
|
||||
runTestWithState(gc, testMain);
|
||||
}
|
||||
|
||||
function shellWrap(s: string) {
|
||||
return "'" + s.replace("\\", "\\\\").replace("'", "\\'") + "'";
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
MerchantService,
|
||||
setupDb,
|
||||
BankService,
|
||||
defaultCoinConfig,
|
||||
} from "./harness";
|
||||
import { AmountString } from "taler-wallet-core/lib/types/talerTypes";
|
||||
|
||||
@ -56,14 +57,8 @@ export async function createSimpleTestkudosEnvironment(
|
||||
currency: "TESTKUDOS",
|
||||
database: db.connStr,
|
||||
httpPort: 8082,
|
||||
suggestedExchange: "http://localhost:8081/",
|
||||
suggestedExchangePayto: "payto://x-taler-bank/MyExchange",
|
||||
});
|
||||
|
||||
await bank.start();
|
||||
|
||||
await bank.pingUntilAvailable();
|
||||
|
||||
const exchange = ExchangeService.create(t, {
|
||||
name: "testexchange-1",
|
||||
currency: "TESTKUDOS",
|
||||
@ -71,11 +66,6 @@ export async function createSimpleTestkudosEnvironment(
|
||||
database: db.connStr,
|
||||
});
|
||||
|
||||
await exchange.setupTestBankAccount(bank, "1", "MyExchange", "x");
|
||||
|
||||
await exchange.start();
|
||||
await exchange.pingUntilAvailable();
|
||||
|
||||
const merchant = await MerchantService.create(t, {
|
||||
name: "testmerchant-1",
|
||||
currency: "TESTKUDOS",
|
||||
@ -83,6 +73,18 @@ export async function createSimpleTestkudosEnvironment(
|
||||
database: db.connStr,
|
||||
});
|
||||
|
||||
bank.setSuggestedExchange(exchange, "payto://x-taler-bank/MyExchange");
|
||||
|
||||
await bank.start();
|
||||
|
||||
await bank.pingUntilAvailable();
|
||||
|
||||
await exchange.setupTestBankAccount(bank, "1", "MyExchange", "x");
|
||||
exchange.addOfferedCoins(defaultCoinConfig);
|
||||
|
||||
await exchange.start();
|
||||
await exchange.pingUntilAvailable();
|
||||
|
||||
merchant.addExchange(exchange);
|
||||
|
||||
await merchant.start();
|
||||
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2020 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
GlobalTestState,
|
||||
BankService,
|
||||
ExchangeService,
|
||||
MerchantService,
|
||||
WalletCli,
|
||||
runTestWithState,
|
||||
} from "./harness";
|
||||
import { withdrawViaBank } from "./helpers";
|
||||
import fs from "fs";
|
||||
|
||||
let existingTestDir =
|
||||
process.env["TALER_TEST_OLD_DIR"] ?? "/tmp/taler-integrationtest-current";
|
||||
|
||||
if (!fs.existsSync(existingTestDir)) {
|
||||
throw Error("old test dir not found");
|
||||
}
|
||||
|
||||
existingTestDir = fs.realpathSync(existingTestDir);
|
||||
|
||||
const prevT = new GlobalTestState({
|
||||
testDir: existingTestDir,
|
||||
});
|
||||
|
||||
/**
|
||||
* Run test.
|
||||
*/
|
||||
runTestWithState(prevT, async (t: GlobalTestState) => {
|
||||
// Set up test environment
|
||||
|
||||
const bank = BankService.fromExistingConfig(t);
|
||||
const exchange = ExchangeService.fromExistingConfig(t, "testexchange-1");
|
||||
const merchant = MerchantService.fromExistingConfig(t, "testmerchant-1");
|
||||
|
||||
await bank.start();
|
||||
await exchange.start();
|
||||
await merchant.start();
|
||||
await Promise.all([
|
||||
bank.pingUntilAvailable(),
|
||||
merchant.pingUntilAvailable(),
|
||||
exchange.pingUntilAvailable(),
|
||||
]);
|
||||
|
||||
const wallet = new WalletCli(t);
|
||||
|
||||
// Withdraw digital cash into the wallet.
|
||||
|
||||
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:100" });
|
||||
|
||||
// Set up order.
|
||||
|
||||
const orderResp = await merchant.createOrder("default", {
|
||||
order: {
|
||||
summary: "Buy me!",
|
||||
amount: "TESTKUDOS:80",
|
||||
fulfillment_url: "taler://fulfillment-success/thx",
|
||||
},
|
||||
});
|
||||
|
||||
let orderStatus = await merchant.queryPrivateOrderStatus(
|
||||
"default",
|
||||
orderResp.order_id,
|
||||
);
|
||||
|
||||
t.assertTrue(orderStatus.order_status === "unpaid");
|
||||
|
||||
// Make wallet pay for the order
|
||||
|
||||
const r1 = await wallet.apiRequest("preparePay", {
|
||||
talerPayUri: orderStatus.taler_pay_uri,
|
||||
});
|
||||
t.assertTrue(r1.type === "response");
|
||||
|
||||
const r2 = await wallet.apiRequest("confirmPay", {
|
||||
// FIXME: should be validated, don't cast!
|
||||
proposalId: (r1.result as any).proposalId,
|
||||
});
|
||||
t.assertTrue(r2.type === "response");
|
||||
|
||||
// Check if payment was successful.
|
||||
|
||||
orderStatus = await merchant.queryPrivateOrderStatus(
|
||||
"default",
|
||||
orderResp.order_id,
|
||||
);
|
||||
|
||||
t.assertTrue(orderStatus.order_status === "paid");
|
||||
|
||||
await t.shutdown();
|
||||
});
|
@ -29,6 +29,7 @@ import {
|
||||
setupDb,
|
||||
BankService,
|
||||
WalletCli,
|
||||
defaultCoinConfig,
|
||||
} from "./harness";
|
||||
import { FaultInjectedExchangeService, FaultInjectionRequestContext, FaultInjectionResponseContext } from "./faultInjection";
|
||||
import { CoreApiResponse } from "taler-wallet-core/lib/walletCoreApiHandler";
|
||||
@ -46,14 +47,8 @@ runTest(async (t: GlobalTestState) => {
|
||||
currency: "TESTKUDOS",
|
||||
database: db.connStr,
|
||||
httpPort: 8082,
|
||||
suggestedExchange: "http://localhost:8091/",
|
||||
suggestedExchangePayto: "payto://x-taler-bank/MyExchange",
|
||||
});
|
||||
|
||||
await bank.start();
|
||||
|
||||
await bank.pingUntilAvailable();
|
||||
|
||||
const exchange = ExchangeService.create(t, {
|
||||
name: "testexchange-1",
|
||||
currency: "TESTKUDOS",
|
||||
@ -61,7 +56,14 @@ runTest(async (t: GlobalTestState) => {
|
||||
database: db.connStr,
|
||||
});
|
||||
|
||||
bank.setSuggestedExchange(exchange, "payto://x-taler-bank/MyExchange");
|
||||
|
||||
await bank.start();
|
||||
|
||||
await bank.pingUntilAvailable();
|
||||
|
||||
await exchange.setupTestBankAccount(bank, "1", "MyExchange", "x");
|
||||
exchange.addOfferedCoins(defaultCoinConfig);
|
||||
|
||||
await exchange.start();
|
||||
await exchange.pingUntilAvailable();
|
||||
|
@ -28,16 +28,13 @@ import {
|
||||
coin_ct10,
|
||||
coin_u1,
|
||||
} from "./harness";
|
||||
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
|
||||
|
||||
/**
|
||||
* Run test.
|
||||
*
|
||||
* This test uses a very sub-optimal denomination structure.
|
||||
*/
|
||||
runTest(async (t: GlobalTestState) => {
|
||||
// Set up test environment
|
||||
import { withdrawViaBank } from "./helpers";
|
||||
|
||||
async function setupTest(t: GlobalTestState): Promise<{
|
||||
merchant: MerchantService,
|
||||
exchange: ExchangeService,
|
||||
bank: BankService,
|
||||
}> {
|
||||
const db = await setupDb(t);
|
||||
|
||||
const bank = await BankService.create(t, {
|
||||
@ -45,22 +42,23 @@ runTest(async (t: GlobalTestState) => {
|
||||
currency: "TESTKUDOS",
|
||||
database: db.connStr,
|
||||
httpPort: 8082,
|
||||
suggestedExchange: "http://localhost:8081/",
|
||||
suggestedExchangePayto: "payto://x-taler-bank/MyExchange",
|
||||
});
|
||||
|
||||
await bank.start();
|
||||
|
||||
await bank.pingUntilAvailable();
|
||||
|
||||
const exchange = ExchangeService.create(t, {
|
||||
name: "testexchange-1",
|
||||
currency: "TESTKUDOS",
|
||||
httpPort: 8081,
|
||||
database: db.connStr,
|
||||
coinConfig: [coin_ct10, coin_u1],
|
||||
});
|
||||
|
||||
exchange.addOfferedCoins([coin_ct10, coin_u1]);
|
||||
|
||||
bank.setSuggestedExchange(exchange, "payto://x-taler-bank/MyExchange");
|
||||
|
||||
await bank.start();
|
||||
|
||||
await bank.pingUntilAvailable();
|
||||
|
||||
await exchange.setupTestBankAccount(bank, "1", "MyExchange", "x");
|
||||
|
||||
await exchange.start();
|
||||
@ -92,6 +90,23 @@ runTest(async (t: GlobalTestState) => {
|
||||
|
||||
console.log("setup done!");
|
||||
|
||||
return {
|
||||
merchant,
|
||||
bank,
|
||||
exchange,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run test.
|
||||
*
|
||||
* This test uses a very sub-optimal denomination structure.
|
||||
*/
|
||||
runTest(async (t: GlobalTestState) => {
|
||||
// Set up test environment
|
||||
|
||||
const { merchant, bank, exchange } = await setupTest(t);
|
||||
|
||||
const wallet = new WalletCli(t);
|
||||
|
||||
// Withdraw digital cash into the wallet.
|
||||
|
@ -26,7 +26,6 @@
|
||||
import { AmountJson } from "./amounts";
|
||||
import * as Amounts from "./amounts";
|
||||
import fs from "fs";
|
||||
import { acceptExchangeTermsOfService } from "../operations/exchanges";
|
||||
|
||||
export class ConfigError extends Error {
|
||||
constructor(message: string) {
|
||||
@ -56,6 +55,26 @@ export class ConfigValue<T> {
|
||||
}
|
||||
return this.converter(this.val);
|
||||
}
|
||||
|
||||
orUndefined(): T | undefined {
|
||||
if (this.val !== undefined) {
|
||||
return this.converter(this.val);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
orDefault(v: T): T | undefined {
|
||||
if (this.val !== undefined) {
|
||||
return this.converter(this.val);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
isDefined(): boolean {
|
||||
return this.val !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,7 +216,7 @@ export class Configuration {
|
||||
getString(section: string, option: string): ConfigValue<string> {
|
||||
const secNorm = section.toUpperCase();
|
||||
const optNorm = option.toUpperCase();
|
||||
const val = (this.sectionMap[section] ?? {})[optNorm];
|
||||
const val = (this.sectionMap[secNorm] ?? {})[optNorm];
|
||||
return new ConfigValue(secNorm, optNorm, val, (x) => x);
|
||||
}
|
||||
|
||||
@ -210,6 +229,36 @@ export class Configuration {
|
||||
);
|
||||
}
|
||||
|
||||
getYesNo(section: string, option: string): ConfigValue<boolean> {
|
||||
const secNorm = section.toUpperCase();
|
||||
const optNorm = option.toUpperCase();
|
||||
const val = (this.sectionMap[secNorm] ?? {})[optNorm];
|
||||
const convert = (x: string): boolean => {
|
||||
x = x.toLowerCase();
|
||||
if (x === "yes") {
|
||||
return true;
|
||||
} else if (x === "no") {
|
||||
return false;
|
||||
}
|
||||
throw Error(`invalid config value for [${secNorm}]/${optNorm}, expected yes/no`);
|
||||
};
|
||||
return new ConfigValue(secNorm, optNorm, val, convert);
|
||||
}
|
||||
|
||||
getNumber(section: string, option: string): ConfigValue<number> {
|
||||
const secNorm = section.toUpperCase();
|
||||
const optNorm = option.toUpperCase();
|
||||
const val = (this.sectionMap[secNorm] ?? {})[optNorm];
|
||||
const convert = (x: string): number => {
|
||||
try {
|
||||
return Number.parseInt(x, 10);
|
||||
} catch (e) {
|
||||
throw Error(`invalid config value for [${secNorm}]/${optNorm}, expected number`);
|
||||
}
|
||||
};
|
||||
return new ConfigValue(secNorm, optNorm, val, convert);
|
||||
}
|
||||
|
||||
lookupVariable(x: string, depth: number = 0): string | undefined {
|
||||
// We loop up options in PATHS in upper case, as option names
|
||||
// are case insensitive
|
||||
|
Loading…
Reference in New Issue
Block a user