diff options
author | Florian Dold <florian@dold.me> | 2024-01-29 12:24:46 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-01-29 12:24:46 +0100 |
commit | 64e340541ffcf10df4ef6400232c423aaecf81b9 (patch) | |
tree | 849ed0ec0e7d075ecf6f9cba718671ba9eb7fa7d | |
parent | 9dc9f464689d4294a1767e35fdae88e9689298c8 (diff) | |
download | wallet-core-64e340541ffcf10df4ef6400232c423aaecf81b9.tar.xz |
wallet-core: implement db migration check
m--------- | build-system/taler-build-scripts | 0 | ||||
-rw-r--r-- | packages/taler-harness/src/harness/harness.ts | 34 | ||||
-rw-r--r-- | packages/taler-harness/src/harness/helpers.ts | 2 | ||||
-rw-r--r-- | packages/taler-harness/src/index.ts | 115 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 6 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet-api-types.ts | 5 |
6 files changed, 143 insertions, 19 deletions
diff --git a/build-system/taler-build-scripts b/build-system/taler-build-scripts -Subproject 001f5dd081fc8729ff8def90c4a1c3f93eb8689 +Subproject 23538677f6c6be2a62f38dc6137ecdd1c76b7b1 diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index b2714f496..b9164a968 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -1885,16 +1885,39 @@ function tryUnixConnect(socketPath: string): Promise<void> { export interface WalletServiceOptions { useInMemoryDb?: boolean; + /** + * Use a particular DB path instead of the default one in the + * test environment. + */ + overrideDbPath?: string; name: string; } +/** + * A wallet service that listens on a unix domain socket for commands. + */ export class WalletService { walletProc: ProcessWrapper | undefined; + private internalDbPath: string; + constructor( private globalState: GlobalTestState, private opts: WalletServiceOptions, - ) {} + ) { + if (this.opts.overrideDbPath) { + this.internalDbPath = this.opts.overrideDbPath; + } else { + if (this.opts.useInMemoryDb) { + this.internalDbPath = ":memory:"; + } else { + this.internalDbPath = path.join( + this.globalState.testDir, + `walletdb-${this.opts.name}.sqlite3`, + ); + } + } + } get socketPath() { const unixPath = path.join( @@ -1905,14 +1928,7 @@ export class WalletService { } get dbPath() { - if (this.opts.useInMemoryDb) { - return ":memory:"; - } else { - return path.join( - this.globalState.testDir, - `walletdb-${this.opts.name}.sqlite3`, - ); - } + return this.internalDbPath; } async stop(): Promise<void> { diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index 7daa6c3c5..bb6f28246 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -405,6 +405,7 @@ export interface CreateWalletArgs { handleNotification?(wn: WalletNotification): void; name: string; persistent?: boolean; + overrideDbPath?: string; } export async function createWalletDaemonWithClient( @@ -414,6 +415,7 @@ export async function createWalletDaemonWithClient( const walletService = new WalletService(t, { name: args.name, useInMemoryDb: !args.persistent, + overrideDbPath: args.overrideDbPath, }); await walletService.start(); await walletService.pingUntilAvailable(); diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index 5a0ccbd12..787ea1933 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -20,6 +20,7 @@ import { AmountString, Amounts, + BalancesResponse, Configuration, Duration, HttpStatusCode, @@ -28,6 +29,7 @@ import { MerchantInstanceConfig, RegisterAccountRequest, TalerCorebankApiClient, + TransactionsResponse, addPaytoQueryParams, decodeCrock, generateIban, @@ -58,12 +60,16 @@ import { runEnvFull } from "./env-full.js"; import { runEnv1 } from "./env1.js"; import { GlobalTestState, + WalletClient, delayMs, runTestWithState, } from "./harness/harness.js"; import { getTestInfo, runTests } from "./integrationtests/testrunner.js"; import { lintExchangeDeployment } from "./lint.js"; -import { createSimpleTestkudosEnvironmentV2 } from "./harness/helpers.js"; +import { + createSimpleTestkudosEnvironmentV2, + createWalletDaemonWithClient, +} from "./harness/helpers.js"; const logger = new Logger("taler-harness:index.ts"); @@ -179,6 +185,88 @@ advancedCli await runTestWithState(testState, runEnv1, "env1", true); }); +async function doDbChecks( + t: GlobalTestState, + walletClient: WalletClient, + indir: string, +): Promise<void> { + // Check that balance didn't break + const balPath = `${indir}/wallet-balances.json`; + const expectedBal: BalancesResponse = JSON.parse( + fs.readFileSync(balPath, { encoding: "utf8" }), + ) as BalancesResponse; + const actualBal = await walletClient.call(WalletApiOperation.GetBalances, {}); + t.assertDeepEqual(actualBal.balances.length, expectedBal.balances.length); + + // Check that transactions didn't break + const txnPath = `${indir}/wallet-transactions.json`; + const expectedTxn: TransactionsResponse = JSON.parse( + fs.readFileSync(txnPath, { encoding: "utf8" }), + ) as TransactionsResponse; + const actualTxn = await walletClient.call( + WalletApiOperation.GetTransactions, + { includeRefreshes: true }, + ); + t.assertDeepEqual( + actualTxn.transactions.length, + expectedTxn.transactions.length, + ); +} + +advancedCli + .subcommand("walletDbcheck", "wallet-dbcheck", { + help: "Check a wallet database (used for migration testing).", + }) + .requiredArgument("indir", clk.STRING) + .action(async (args) => { + const indir = args.walletDbcheck.indir; + if (!fs.existsSync(indir)) { + throw Error("directory to be checked does not exist"); + } + + const testRootDir = fs.mkdtempSync(path.join(os.tmpdir(), "taler-dbchk-")); + const t: GlobalTestState = new GlobalTestState({ + testDir: testRootDir, + }); + const walletDbPath = `${indir}/wallet-db.sqlite3`; + if (!fs.existsSync(walletDbPath)) { + throw new Error("wallet db to be checked does not exist"); + } + const { walletClient, walletService } = await createWalletDaemonWithClient( + t, + { name: "wallet-loaded", overrideDbPath: walletDbPath }, + ); + + await walletService.pingUntilAvailable(); + + // Do DB checks with the DB we loaded. + await doDbChecks(t, walletClient, indir); + + const { + walletClient: freshWalletClient, + walletService: freshWalletService, + } = await createWalletDaemonWithClient(t, { + name: "wallet-fresh", + persistent: false, + }); + + // Check that we can still import the backup JSON. + + const backupPath = `${indir}/wallet-backup.json`; + const backupData = JSON.parse( + fs.readFileSync(backupPath, { encoding: "utf8" }), + ); + await freshWalletClient.call(WalletApiOperation.ImportDb, { + dump: backupData, + }); + + // Repeat same checks with wallet that we restored from backup + // instead of from the DB file. + await doDbChecks(t, freshWalletClient, indir); + + await t.shutdown(); + }); + advancedCli .subcommand("walletDbgen", "wallet-dbgen", { help: "Generate a wallet test database (to be used for migration testing).", @@ -186,6 +274,9 @@ advancedCli .requiredArgument("outdir", clk.STRING) .action(async (args) => { const outdir = args.walletDbgen.outdir; + if (fs.existsSync(outdir)) { + throw new Error("outdir already exists, please delete first"); + } fs.mkdirSync(outdir, { recursive: true, }); @@ -209,16 +300,24 @@ advancedCli {}, ); - const transactionsJson = walletClient.call( + const transactionsJson = await walletClient.call( WalletApiOperation.GetTransactions, { includeRefreshes: true, }, ); - const balancesJson = walletClient.call(WalletApiOperation.GetBalances, {}); + const balancesJson = await walletClient.call( + WalletApiOperation.GetBalances, + {}, + ); + + const backupJson = await walletClient.call(WalletApiOperation.ExportDb, {}); - const backupJson = walletClient.call(WalletApiOperation.ExportDb, {}); + const versionJson = await walletClient.call( + WalletApiOperation.GetVersion, + {}, + ); await walletService.stop(); @@ -233,6 +332,13 @@ advancedCli ); fs.writeFileSync(`${outdir}/wallet-balances.json`, j2s(balancesJson)); fs.writeFileSync(`${outdir}/wallet-backup.json`, j2s(backupJson)); + fs.writeFileSync(`${outdir}/wallet-version.json`, j2s(versionJson)); + fs.writeFileSync( + `${outdir}/meta.json`, + j2s({ + timestamp: new Date(), + }), + ); }); const configCli = testingCli.subcommand("configArgs", "config", { @@ -317,7 +423,6 @@ deploymentCli const paytoUri = addPaytoQueryParams(tipReserveResp.accounts[0].payto_uri, { message: `tip-reserve ${tipReserveResp.reserve_pub}`, }); - console.log("payto URI:", paytoUri); const transactions = await bankAccessApiClient.getTransactions( diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 524dfe3e0..806e2f22b 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -2476,12 +2476,12 @@ export const codecForWithdrawFakebankRequest = .property("exchange", codecForString()) .build("WithdrawFakebankRequest"); -export interface ImportDb { +export interface ImportDbRequest { dump: any; } -export const codecForImportDbRequest = (): Codec<ImportDb> => - buildCodecForObject<ImportDb>() +export const codecForImportDbRequest = (): Codec<ImportDbRequest> => + buildCodecForObject<ImportDbRequest>() .property("dump", codecForAny()) .build("ImportDbRequest"); diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 594d5e14d..cc67781ae 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -139,6 +139,7 @@ import { AddGlobalCurrencyAuditorRequest, RemoveGlobalCurrencyExchangeRequest, RemoveGlobalCurrencyAuditorRequest, + ImportDbRequest, } from "@gnu-taler/taler-util"; import { AddBackupProviderRequest, @@ -968,8 +969,8 @@ export type ExportDbOp = { export type ImportDbOp = { op: WalletApiOperation.ImportDb; - request: any; - response: any; + request: ImportDbRequest; + response: EmptyObject; }; /** |