diff --git a/dens-testsuites/integration/bin/dens-integration-cli.js b/dens-testsuites/integration/bin/dens-integration-cli.js new file mode 100755 index 0000000..38b4e21 --- /dev/null +++ b/dens-testsuites/integration/bin/dens-integration-cli.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import "../dist/lib/services.js"; + +await Services.spawn(); diff --git a/dens-testsuites/integration/package.json b/dens-testsuites/integration/package.json index 3a534d0..d8e1c7f 100644 --- a/dens-testsuites/integration/package.json +++ b/dens-testsuites/integration/package.json @@ -2,6 +2,10 @@ "name": "dens-integration", "version": "1.0.0", "description": "Integration testsuite for DeNS", + "type": "module", + "bin": { + "dens-integration-cli": "./bin/dens-integration-cli.js" + }, "scripts": { "build": "npx tsc -b src/", "test": "npm run build && node --test" diff --git a/dens-testsuites/integration/src/lib/services.ts b/dens-testsuites/integration/src/lib/services.ts index 00da858..1e8c4d1 100644 --- a/dens-testsuites/integration/src/lib/services.ts +++ b/dens-testsuites/integration/src/lib/services.ts @@ -12,11 +12,16 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as timers from "timers/promises"; import * as path from "node:path"; +import { Config } from "lbf-dens-db/LambdaBuffers/Dens/Config.mjs"; import chalk from "chalk"; import * as utils from "./utils.js"; -// import * as Pla from "plutus-ledger-api/V1.js"; + +import * as PlaV1 from "plutus-ledger-api/V1.js"; +import * as P from "prelude"; +import * as PJson from "prelude/Json.js"; +import * as LbrPrelude from "lbr-prelude"; interface Cardano { /** Path to UNIX domain socket that the cardano node is running on */ @@ -44,6 +49,13 @@ interface Ogmios { childProcess: ChildProcess; } +interface DensQuery { + /** Path to UNIX domain socket that dens query is listening on */ + socketPath: string; + + childProcess: ChildProcess; +} + /** * {@link Services} a class to */ @@ -51,11 +63,18 @@ export class Services { cardano: Cardano; database: Database; ogmios: Ogmios; - - constructor(cardano: Cardano, database: Database, ogmios: Ogmios) { + densQuery: DensQuery; + + constructor( + cardano: Cardano, + database: Database, + ogmios: Ogmios, + densQuery: DensQuery, + ) { this.cardano = cardano; this.database = database; this.ogmios = ogmios; + this.densQuery = densQuery; } /** @@ -68,30 +87,20 @@ export class Services { spawnDatabase(), ]); const ogmios = await spawnOgmios(cardano); - return new Services(cardano, database, ogmios); + const densQuery = await spawnDensQuery(database, ogmios); + return new Services(cardano, database, ogmios, densQuery); } kill(): Promise { this.cardano.childProcess.kill("SIGINT"); this.database.childProcess.kill("SIGINT"); // See {@link https://www.postgresql.org/docs/current/server-shutdown.html} this.ogmios.childProcess.kill("SIGINT"); + this.densQuery.childProcess.kill("SIGINT"); + + return Promise.resolve(); } } -// export class DensQueryService { -// static async spawn( -// protocolNft: [string], -// services: Services, -// ): Promise { -// const [cardano, database] = await Promise.all([ -// spawnPlutip(numWallets), -// spawnDatabase(), -// ]); -// const ogmios = await spawnOgmios(cardano); -// return new Services(cardano, database, ogmios); -// } -// } - /** * This is needed because of the awkwardness of knowing when plutip is * initialized... Plutip will dump a bunch of files to the file system, and we @@ -156,6 +165,108 @@ async function pollReadJsonFile(filePath: string): Promise { return result; } +async function spawnDensQuery( + database: Database, + ogmios: Ogmios, +): Promise { + // Create the database user / names + const densUserName = `dens`; + const createDensUserProcess = child_process.spawn(`createuser`, [ + `-h`, + database.socketPath, + `-d`, + `-r`, + densUserName, + ], { stdio: ["ignore", "ignore", "inherit"] }); + + const densDatabase = `dens`; + + const createDensDbProcess = child_process.spawn(`createdb`, [ + `-h`, + database.socketPath, + `-O`, + densUserName, + densDatabase, + ], { stdio: ["ignore", "ignore", "inherit"] }); + + await new Promise((resolve, reject) => + createDensUserProcess.on("close", (code) => { + if (code !== 0) { + return reject(new Error(`createuser failed`)); + } + resolve(); + }) + ); + await new Promise((resolve, reject) => + createDensDbProcess.on("close", (code) => { + if (code !== 0) { + return reject(new Error(`createdb failed`)); + } + resolve(); + }) + ); + + // Create a temporary directory to put dens-query's files + const densQueryDir = await fs.mkdtemp(path.join(os.tmpdir(), `dens-query-`)); + console.error(chalk.blue(`dens-query working directory:\n\t${densQueryDir}`)); + + // Create the database user / names + const socketPath = path.join(densQueryDir, `.s.dens-query`); + const zeroCurrencySymbolBytes = []; + for (let i = 0; i < 28; ++i) { + zeroCurrencySymbolBytes.push(0); + } + + const config: Config = { + ogmios: { url: `ws://${ogmios.host}:${ogmios.port}` }, + database: { + socket: { name: `UnixDomain`, fields: { path: database.socketPath } }, + user: densUserName, + password: ``, + database: densDatabase, + }, + server: { name: `UnixDomain`, fields: { path: socketPath } }, + protocolNft: [ + P.fromJust( + PlaV1.currencySymbolFromBytes(Uint8Array.from(zeroCurrencySymbolBytes)), + ), + PlaV1.adaToken, + ], + }; + + const configFileName = path.join(densQueryDir, `config.json`); + await fs.appendFile( + configFileName, + PJson.stringify(LbrPrelude.Json[Config].toJson(config)), + ); + + // Start dens-query + const env = JSON.parse(JSON.stringify(process.env)); // silly way to copy the current environment + env["DENS_QUERY_CONFIG"] = configFileName; + + const densQueryProcess = child_process.spawn(`dens-query-cli`, [], { + env, + stdio: [`inherit`, `inherit`, `inherit`], + cwd: densQueryDir, + }); + + densQueryProcess.on("close", (code, signal) => { + if (code === 0 || signal === "SIGINT") { + return; + } + throw new Error(`dens-query-cli failed with exit code ${code}`); + }); + + // FIXME(jaredponn): we wait 15 seconds to let initialize. Change this to poll + // dens-query until it replies + await timers.setTimeout(15000); + + return { socketPath, childProcess: densQueryProcess }; +} + +/** + * Spawns an ogmios connection + */ function spawnOgmios(cardano: Cardano): Promise { // See {@link https://www.rfc-editor.org/rfc/rfc6335.html}'s Dynamic ports // for why we choose this range @@ -173,9 +284,13 @@ function spawnOgmios(cardano: Cardano): Promise { host, `--port`, port.toString(), - ]); + ], { stdio: [`ignore`, `ignore`, `inherit`] }); - return { host, port: port.toString(), childProcess: ogmiosProcess }; + return Promise.resolve({ + host, + port: port.toString(), + childProcess: ogmiosProcess, + }); } /** @@ -186,6 +301,16 @@ async function spawnDatabase(): Promise { const postgresDir = await fs.mkdtemp(path.join(os.tmpdir(), `postgres-`)); console.error(chalk.blue(`Postgres working directory:\n\t${postgresDir}`)); + console.error( + chalk.blue( + `To restart Postgres execute\n\tcd ${postgresDir} && postgres -h '' -k ${postgresDir} -D ${postgresDir}`, + ), + ); + console.error( + chalk.blue( + `To connect to Postgres via psql execute:\n\tpsql -h ${postgresDir}`, + ), + ); { // Use `initdb` to initialize the database @@ -222,7 +347,7 @@ async function spawnDatabase(): Promise { postgresDir, `-r`, path.join(postgresDir, `logs.output`), - ], { stdio: [`ignore`, `ignore`, `inherit`] }); + ], { stdio: [`ignore`, `ignore`, `ignore`] }); // Somewhere in the postgres docs it says this is what the socket is // named @@ -280,7 +405,7 @@ async function spawnPlutip(numWallets: number): Promise { numWallets.toString(), `--wallets-dir`, plutipWalletsDir, - ], { stdio: [`ignore`, `ignore`, `inherit`] }); + ], { stdio: [`ignore`, `ignore`, `ignore`] }); result.childProcess = childProcess; diff --git a/dens-testsuites/integration/src/tests/more-valid-test.ts b/dens-testsuites/integration/src/tests/more-valid-test.ts deleted file mode 100644 index 1d89174..0000000 --- a/dens-testsuites/integration/src/tests/more-valid-test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import test from "node:test"; -import { Services } from "../lib/services.js"; - -test.describe("Yet another runtime services can be initialized", async () => { - let result: Services | undefined; - await test.before(async () => { - result = await Services.spawn(); - }); - await test.after(async () => { - await result!.kill(); - }); -}); diff --git a/dens-testsuites/integration/src/tests/valid-test.ts b/dens-testsuites/integration/src/tests/valid-test.ts index b52332a..c3226ff 100644 --- a/dens-testsuites/integration/src/tests/valid-test.ts +++ b/dens-testsuites/integration/src/tests/valid-test.ts @@ -2,11 +2,12 @@ import test from "node:test"; import { Services } from "../lib/services.js"; test.describe("Runtime services can be initialized", async () => { - let result: Services | undefined; + let services: Services | undefined; await test.before(async () => { - result = await Services.spawn(); + services = await Services.spawn(); }); + await test.after(async () => { - await result!.kill(); + await services!.kill(); }); });