@holochain/tryorama
Version:
Toolset to manage Holochain conductors and facilitate running test scenarios
152 lines (151 loc) • 5.03 kB
JavaScript
import assert from "node:assert";
import { v4 as uuidv4 } from "uuid";
import { addAllAgentsToAllConductors, enableAndGetAgentApp, runLocalServices, stopLocalServices, } from "../common.js";
import { cleanAllConductors, createConductor } from "./conductor.js";
/**
* An abstraction of a test scenario to write tests against Holochain hApps,
* running on a local conductor.
*
* @public
*/
export class Scenario {
timeout;
noDpki;
dpkiNetworkSeed;
networkSeed;
serviceProcess;
bootstrapServerUrl;
signalingServerUrl;
conductors;
/**
* Scenario constructor.
*
* @param options - Timeout for requests to Admin and App API calls.
*/
constructor(options) {
this.timeout = options?.timeout;
this.noDpki = false;
this.dpkiNetworkSeed = uuidv4();
this.networkSeed = uuidv4();
this.serviceProcess = undefined;
this.bootstrapServerUrl = undefined;
this.signalingServerUrl = undefined;
this.conductors = [];
}
/**
* Create and add a conductor to the scenario.
*
* @returns The newly added conductor instance.
*/
async addConductor() {
await this.ensureLocalServices();
assert(this.serviceProcess);
assert(this.signalingServerUrl);
const conductor = await createConductor(this.signalingServerUrl, {
timeout: this.timeout,
bootstrapServerUrl: this.bootstrapServerUrl,
});
this.conductors.push(conductor);
return conductor;
}
/**
* Create and add a single player with an app installed to the scenario.
*
* @param appBundleSource - The bundle or path to the bundle.
* @param options - {@link AppOptions}.
* @returns A local player instance.
*/
async addPlayerWithApp(appBundleSource, options) {
await this.ensureLocalServices();
const conductor = await this.addConductor();
options = {
...options,
networkSeed: options?.networkSeed ?? this.networkSeed,
};
const appInfo = await conductor.installApp(appBundleSource, options);
const adminWs = conductor.adminWs();
const port = await conductor.attachAppInterface();
const issued = await adminWs.issueAppAuthenticationToken({
installed_app_id: appInfo.installed_app_id,
});
const appWs = await conductor.connectAppWs(issued.token, port);
const agentApp = await enableAndGetAgentApp(adminWs, appWs, appInfo);
return { conductor, appWs, ...agentApp };
}
/**
* Create and add multiple players to the scenario, with an app installed
* for each player.
*
* @param playersApps - An array with an app for each player.
* @returns All created players.
*/
async addPlayersWithApps(playersApps) {
await this.ensureLocalServices();
return await Promise.all(playersApps.map((playerApp) => this.addPlayerWithApp(playerApp.appBundleSource, playerApp.options)));
}
/**
* Register all agents of all passed in conductors to each other. This skips
* peer discovery through gossip and thus accelerates test runs.
*
* @public
*/
async shareAllAgents() {
return addAllAgentsToAllConductors(this.conductors);
}
/**
* Shut down all conductors in the scenario.
*/
async shutDown() {
await Promise.all(this.conductors.map((conductor) => conductor.shutDown()));
if (this.serviceProcess) {
await stopLocalServices(this.serviceProcess);
}
}
/**
* Shut down and delete all conductors in the scenario.
*/
async cleanUp() {
await this.shutDown();
await cleanAllConductors();
this.conductors = [];
this.serviceProcess = undefined;
this.bootstrapServerUrl = undefined;
this.signalingServerUrl = undefined;
}
async ensureLocalServices() {
if (!this.serviceProcess) {
({
servicesProcess: this.serviceProcess,
bootstrapServerUrl: this.bootstrapServerUrl,
signalingServerUrl: this.signalingServerUrl,
} = await runLocalServices());
}
}
}
/**
* A wrapper function to create and run a scenario. A scenario is created
* and all involved conductors are shut down and cleaned up after running.
*
* @param testScenario - The test to be run.
* @param cleanUp - Whether to delete conductors after running. @defaultValue true
*
* @public
*/
export const runScenario = async (testScenario, cleanUp = true, options) => {
const scenario = new Scenario(options);
try {
await testScenario(scenario);
}
catch (error) {
console.error("error occurred during test run:", error);
throw error;
}
finally {
if (cleanUp) {
await scenario.cleanUp();
}
else {
await scenario.shutDown();
}
}
};