diff options
-rw-r--r-- | packages/taler-integrationtests/src/harness.ts | 177 | ||||
-rw-r--r-- | packages/taler-integrationtests/src/test-timetravel.ts | 64 |
2 files changed, 219 insertions, 22 deletions
diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts index f4429d43f..5fd642e33 100644 --- a/packages/taler-integrationtests/src/harness.ts +++ b/packages/taler-integrationtests/src/harness.ts @@ -511,16 +511,10 @@ export namespace BankAccessApi { bank: BankServiceInterface, bankUser: BankUser, ): Promise<BankAccountBalanceResponse> { - const url = new URL( - `accounts/${bankUser.username}/balance`, - bank.baseUrl, - ); - const resp = await axios.get( - url.href, - { - auth: bankUser, - }, - ); + const url = new URL(`accounts/${bankUser.username}/balance`, bank.baseUrl); + const resp = await axios.get(url.href, { + auth: bankUser, + }); return resp.data; } @@ -636,7 +630,6 @@ export namespace BankApi { }, ); } - } export class BankService implements BankServiceInterface { @@ -813,11 +806,41 @@ export class ExchangeService implements ExchangeServiceInterface { return new ExchangeService(gc, ec, cfgFilename, keyPair); } + private currentTimetravel: Duration | undefined; + + setTimetravel(t: Duration | undefined): void { + if (this.isRunning()) { + throw Error("can't set time travel while the exchange is running"); + } + this.currentTimetravel = t; + } + + private get timetravelArg(): string | undefined { + if (this.currentTimetravel && this.currentTimetravel.d_ms !== "forever") { + // Convert to microseconds + return `--timetravel=+${this.currentTimetravel.d_ms * 1000}`; + } + return undefined; + } + + /** + * Return an empty array if no time travel is set, + * and an array with the time travel command line argument + * otherwise. + */ + private get timetravelArgArr(): string[] { + const tta = this.timetravelArg; + if (tta) { + return [tta]; + } + return []; + } + async runWirewatchOnce() { await sh( this.globalState, "wirewatch-test", - `taler-exchange-wirewatch -c '${this.configFilename}' -t`, + `taler-exchange-wirewatch ${this.timetravelArg} -c '${this.configFilename}' -t`, ); } @@ -965,20 +988,64 @@ export class ExchangeService implements ExchangeServiceInterface { return `http://localhost:${this.exchangeConfig.httpPort}/`; } + isRunning(): boolean { + return !!this.exchangeWirewatchProc || !!this.exchangeHttpProc; + } + + async stop(): Promise<void> { + const wirewatch = this.exchangeWirewatchProc; + if (wirewatch) { + wirewatch.proc.kill("SIGTERM"); + await wirewatch.wait(); + this.exchangeWirewatchProc = undefined; + } + const httpd = this.exchangeHttpProc; + if (httpd) { + httpd.proc.kill("SIGTERM"); + await httpd.wait(); + this.exchangeHttpProc = undefined; + } + } + + async keyup(): Promise<void> { + await sh( + this.globalState, + "exchange-keyup", + `taler-exchange-keyup ${this.timetravelArg} -c "${this.configFilename}"`, + ); + } + async start(): Promise<void> { - await exec(`taler-exchange-dbinit -c "${this.configFilename}"`); - await exec(`taler-exchange-wire -c "${this.configFilename}"`); - await exec(`taler-exchange-keyup -c "${this.configFilename}"`); + if (this.isRunning()) { + throw Error("exchange is already running"); + } + await sh( + this.globalState, + "exchange-dbinit", + `taler-exchange-dbinit -c "${this.configFilename}"`, + ); + await this.keyup(); + await sh( + this.globalState, + "exchange-wire", + `taler-exchange-wire ${this.timetravelArg} -c "${this.configFilename}"` + ) this.exchangeWirewatchProc = this.globalState.spawnService( "taler-exchange-wirewatch", - ["-c", this.configFilename], + ["-c", this.configFilename, ...this.timetravelArgArr], `exchange-wirewatch-${this.name}`, ); this.exchangeHttpProc = this.globalState.spawnService( "taler-exchange-httpd", - ["-c", this.configFilename, "--num-threads", "1"], + [ + "-c", + this.configFilename, + "--num-threads", + "1", + ...this.timetravelArgArr, + ], `exchange-httpd-${this.name}`, ); } @@ -1079,6 +1146,40 @@ export class MerchantService implements MerchantServiceInterface { private configFilename: string, ) {} + private currentTimetravel: Duration | undefined; + + private isRunning(): boolean { + return !!this.proc; + } + + setTimetravel(t: Duration | undefined): void { + if (this.isRunning()) { + throw Error("can't set time travel while the exchange is running"); + } + this.currentTimetravel = t; + } + + private get timetravelArg(): string | undefined { + if (this.currentTimetravel && this.currentTimetravel.d_ms !== "forever") { + // Convert to microseconds + return `--timetravel=+${this.currentTimetravel.d_ms * 1000}`; + } + return undefined; + } + + /** + * Return an empty array if no time travel is set, + * and an array with the time travel command line argument + * otherwise. + */ + private get timetravelArgArr(): string[] { + const tta = this.timetravelArg; + if (tta) { + return [tta]; + } + return []; + } + get port(): number { return this.merchantConfig.httpPort; } @@ -1087,12 +1188,21 @@ export class MerchantService implements MerchantServiceInterface { return this.merchantConfig.name; } + async stop(): Promise<void> { + const httpd = this.proc; + if (httpd) { + httpd.proc.kill("SIGTERM"); + await httpd.wait(); + this.proc = undefined; + } + } + async start(): Promise<void> { await exec(`taler-merchant-dbinit -c "${this.configFilename}"`); this.proc = this.globalState.spawnService( "taler-merchant-httpd", - ["-LINFO", "-c", this.configFilename], + ["-LINFO", "-c", this.configFilename, ...this.timetravelArgArr], `merchant-${this.merchantConfig.name}`, ); } @@ -1266,7 +1376,24 @@ function shellWrap(s: string) { } export class WalletCli { - constructor(private globalTestState: GlobalTestState, private name: string = "default") {} + private currentTimetravel: Duration | undefined; + + setTimetravel(d: Duration | undefined) { + this.currentTimetravel = d; + } + + private get timetravelArg(): string | undefined { + if (this.currentTimetravel && this.currentTimetravel.d_ms !== "forever") { + // Convert to microseconds + return `--timetravel=${this.currentTimetravel.d_ms * 1000}`; + } + return undefined; + } + + constructor( + private globalTestState: GlobalTestState, + private name: string = "default", + ) {} get dbfile(): string { return this.globalTestState.testDir + `/walletdb-${this.name}.json`; @@ -1283,7 +1410,9 @@ export class WalletCli { const resp = await sh( this.globalTestState, `wallet-${this.name}`, - `taler-wallet-cli --no-throttle --wallet-db '${this.dbfile}' api '${request}' ${shellWrap( + `taler-wallet-cli ${ + this.timetravelArg ?? "" + } --no-throttle --wallet-db '${this.dbfile}' api '${request}' ${shellWrap( JSON.stringify(payload), )}`, ); @@ -1295,7 +1424,9 @@ export class WalletCli { await sh( this.globalTestState, `wallet-${this.name}`, - `taler-wallet-cli --no-throttle --wallet-db ${this.dbfile} run-until-done`, + `taler-wallet-cli ${this.timetravelArg ?? ""} --no-throttle --wallet-db ${ + this.dbfile + } run-until-done`, ); } @@ -1303,7 +1434,9 @@ export class WalletCli { await sh( this.globalTestState, `wallet-${this.name}`, - `taler-wallet-cli --no-throttle --wallet-db ${this.dbfile} run-pending`, + `taler-wallet-cli ${this.timetravelArg ?? ""} --no-throttle --wallet-db ${ + this.dbfile + } run-pending`, ); } diff --git a/packages/taler-integrationtests/src/test-timetravel.ts b/packages/taler-integrationtests/src/test-timetravel.ts new file mode 100644 index 000000000..acc770d5d --- /dev/null +++ b/packages/taler-integrationtests/src/test-timetravel.ts @@ -0,0 +1,64 @@ +/* + 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 { runTest, GlobalTestState, MerchantPrivateApi, WalletCli } from "./harness"; +import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; +import { PreparePayResultType, durationMin, Duration } from "taler-wallet-core"; + +/** + * Basic time travel test. + */ +runTest(async (t: GlobalTestState) => { + // Set up test environment + + const { + wallet, + bank, + exchange, + merchant, + } = await createSimpleTestkudosEnvironment(t); + + // Withdraw digital cash into the wallet. + + await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); + + // Travel 400 days into the future, + // as the deposit expiration is two years + // into the future. + const timetravelDuration: Duration = { + d_ms: 1000 * 60 * 60 * 24 * 400, + }; + + await exchange.stop(); + exchange.setTimetravel(timetravelDuration); + await exchange.start(); + await exchange.pingUntilAvailable(); + + await merchant.stop(); + merchant.setTimetravel(timetravelDuration); + await merchant.start(); + await merchant.pingUntilAvailable(); + + // This should fail, as the wallet didn't time travel yet. + await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); + + const bal = await wallet.getBalances(); + + console.log(bal); +}); |