Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/check-package-lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Check Package Lock File

permissions:
contents: read

concurrency:
group: check-package-lock-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches:
- main # Run on push to main branch only
pull_request:
branches:
- "**" # Run on PR to any branch

jobs:
verify-package-lock:
name: Verify package-lock.json exists
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Check if package-lock.json exists
run: |
if [ ! -f "package-lock.json" ]; then
echo "ERROR: package-lock.json file is missing from the repository"
echo "This file is required to ensure consistent dependency versions across all environments"
echo "Please ensure package-lock.json is committed with your changes"
exit 1
fi
echo "SUCCESS: package-lock.json file is present"

- name: Verify package-lock.json is not empty
run: |
if [ ! -s "package-lock.json" ]; then
echo "ERROR: package-lock.json file exists but is empty"
echo "Please run 'npm install' to regenerate the lock file"
exit 1
fi
echo "SUCCESS: package-lock.json file is valid and not empty"

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Validate package-lock.json is valid and in sync
run: npm ci --dry-run --ignore-scripts
5 changes: 5 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ jobs:
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- name: Checkout repository
uses: actions/checkout@v3
Expand Down
6 changes: 6 additions & 0 deletions src/modules/http-client/http-client-module-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ class HttpClientModuleManager extends BaseModuleManager {
return this.getImplementation().module.initializeAfterMiddlewares(this.authService);
}
}

async close() {
if (this.initialized && this.getImplementation().module.close) {
return this.getImplementation().module.close();
}
}
}

export default HttpClientModuleManager;
21 changes: 20 additions & 1 deletion src/modules/http-client/implementation/express-http-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,30 @@ class ExpressHttpClient {
);
this.httpsServer.listen(this.config.port);
} else {
this.app.listen(this.config.port);
this.server = this.app.listen(this.config.port);
}
this.logger.info(`Node listening on port: ${this.config.port}`);
}

async close() {
return new Promise((resolve, reject) => {
const serverToClose = this.httpsServer || this.server;
if (serverToClose) {
serverToClose.close((err) => {
if (err) {
this.logger.error(`Error closing HTTP server: ${err.message}`);
reject(err);
} else {
this.logger.info('HTTP server closed successfully');
resolve();
}
});
} else {
resolve();
}
});
}

selectMiddlewares(options) {
const middlewares = [];
if (options.rateLimit) middlewares.push(rateLimiterMiddleware(this.config.rateLimiter));
Expand Down
14 changes: 14 additions & 0 deletions src/service/claim-rewards-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ class ClaimRewardsService {
}
}

// Add cleanup method to stop intervals
cleanup() {
this.logger.info('[CLAIM] Starting ClaimRewardsService cleanup');
for (const blockchainId of this.blockchainModuleManager.getImplementationNames()) {
const intervalKey = `${blockchainId}Interval`;
if (this[intervalKey]) {
this.logger.debug(`[CLAIM] Clearing interval for blockchain ${blockchainId}`);
clearInterval(this[intervalKey]);
this[intervalKey] = null;
}
}
this.logger.info('[CLAIM] ClaimRewardsService cleanup completed');
}

async claimRewards(blockchainId) {
const identityId = await this.blockchainModuleManager.getIdentityId(blockchainId);
const nodeDelegatorAddresses = await this.blockchainModuleManager.getDelegators(
Expand Down
79 changes: 56 additions & 23 deletions test/bdd/steps/common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,7 @@ Given(
if (response.error) {
assert.fail(`Error while initializing node${nodeIndex}: ${response.error}`);
} else {
// todo if started
const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
maxNumberOfRetries: 5,
frequency: 2,
contentType: 'all',
});
// Build blockchain options BEFORE creating the client
let clientBlockchainOptions = {};
Object.keys(this.state.localBlockchains).forEach((blockchainId, index) => {
const blockchain = this.state.localBlockchains[blockchainId];
Expand All @@ -87,6 +80,25 @@ Given(
};
});

// Get the first blockchain for the DKG client config
const firstBlockchainId = Object.keys(this.state.localBlockchains)[0];
const firstBlockchain = this.state.localBlockchains[firstBlockchainId];
const firstWallet = firstBlockchain.getWallets()[0];

const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
maxNumberOfRetries: 5,
frequency: 2,
contentType: 'all',
blockchain: {
name: firstBlockchainId,
publicKey: firstWallet.address,
privateKey: firstWallet.privateKey,
rpc: `http://localhost:${firstBlockchain.port}`,
},
});

this.state.nodes[nodeIndex] = {
client,
forkedNode,
Expand Down Expand Up @@ -145,23 +157,44 @@ Given(
fs.rmSync(appDataPath, { recursive: true, force: true });

const nodeInstance = new MockOTNode(nodeConfiguration);
await nodeInstance.start(); // This will skip startNetworkModule

try {
await nodeInstance.start(); // This will skip startNetworkModule

// Get the first blockchain for the DKG client config
const firstBlockchainId = Object.keys(this.state.localBlockchains)[0];
const firstBlockchain = this.state.localBlockchains[firstBlockchainId];
const firstWallet = firstBlockchain.getWallets()[0];

const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
useSSL: false,
timeout: 25,
loglevel: 'trace',
});
const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
useSSL: false,
timeout: 25,
loglevel: 'trace',
blockchain: {
name: firstBlockchainId,
publicKey: firstWallet.address,
privateKey: firstWallet.privateKey,
rpc: `http://localhost:${firstBlockchain.port}`,
},
});

this.state.bootstraps.push({
client,
otNodeInstance: nodeInstance,
configuration: nodeConfiguration,
nodeRpcUrl: `http://localhost:${rpcPort}`,
fileService: nodeInstance.fileService,
});
this.state.bootstraps.push({
client,
otNodeInstance: nodeInstance,
configuration: nodeConfiguration,
nodeRpcUrl: `http://localhost:${rpcPort}`,
fileService: nodeInstance.fileService,
});
} catch (error) {
// Ensure node is stopped if there's an error after starting
this.logger.error(`Error during bootstrap initialization: ${error.message}`);
if (nodeInstance.stop) {
await nodeInstance.stop();
}
throw error;
}
}
);
//
Expand Down
68 changes: 44 additions & 24 deletions test/bdd/steps/hooks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ process.env.NODE_ENV = NODE_ENVIRONMENTS.TEST;

BeforeAll(() => {});

Before(function beforeMethod(testCase, done) {
Before(function beforeMethod(testCase) {
this.logger = console;
this.logger.log('\n🟡 Starting scenario:', testCase.pickle.name);
// Initialize variables
Expand All @@ -24,48 +24,58 @@ Before(function beforeMethod(testCase, done) {
fs.mkdirSync(logDir, { recursive: true });
this.state.scenarionLogDir = logDir;
this.logger.log('📁 Scenario logs:', logDir);
done();
});

After(async function afterMethod(testCase, done) {
After({ timeout: 30000 }, async function afterMethod(testCase) {
const tripleStoreConfiguration = [];
const databaseNames = [];
const promises = [];
const cleanupPromises = [];

// Stop all nodes first and wait for them to shut down
const stopPromises = [];

for (const key in this.state.nodes) {
const node = this.state.nodes[key];
if (node.forkedNode) {
node.forkedNode.kill();
} else if (node.otNodeInstance?.stop) {
promises.push(node.otNodeInstance.stop());
stopPromises.push(node.otNodeInstance.stop());
}

tripleStoreConfiguration.push({
modules: { tripleStore: node.configuration.modules.tripleStore },
});
databaseNames.push(node.configuration.operationalDatabase.databaseName);
const dataFolderPath = node.fileService.getDataFolderPath();
promises.push(node.fileService.removeFolder(dataFolderPath));
cleanupPromises.push(node.fileService.removeFolder(dataFolderPath));
}

for (const node of this.state.bootstraps) {
if (node.forkedNode) {
node.forkedNode.kill();
} else if (node.otNodeInstance?.stop) {
promises.push(node.otNodeInstance.stop());
stopPromises.push(node.otNodeInstance.stop());
}

tripleStoreConfiguration.push({
modules: { tripleStore: node.configuration.modules.tripleStore },
});
databaseNames.push(node.configuration.operationalDatabase.databaseName);
const dataFolderPath = node.fileService.getDataFolderPath();
promises.push(node.fileService.removeFolder(dataFolderPath));
cleanupPromises.push(node.fileService.removeFolder(dataFolderPath));
}

// Wait for all nodes to stop before continuing
this.logger.log('⏸️ Stopping all nodes...');
await Promise.all(stopPromises);
this.logger.log('✅ All nodes stopped');

// Give a moment for ports to be fully released
await new Promise(resolve => setTimeout(resolve, 500));

for (const localBlockchain in this.state.localBlockchains) {
this.logger.info(`🛑 Stopping local blockchain ${localBlockchain}`);
promises.push(this.state.localBlockchains[localBlockchain].stop());
cleanupPromises.push(this.state.localBlockchains[localBlockchain].stop());
this.state.localBlockchains[localBlockchain] = null;
}

Expand All @@ -78,38 +88,48 @@ After(async function afterMethod(testCase, done) {

for (const db of databaseNames) {
const sql = `DROP DATABASE IF EXISTS \`${db}\`;`;
promises.push(con.promise().query(sql));
cleanupPromises.push(con.promise().query(sql));
}

for (const config of tripleStoreConfiguration) {
promises.push((async () => {
const tripleStoreModuleManager = new TripleStoreModuleManager({
config,
logger: this.logger,
});
await tripleStoreModuleManager.initialize();
for (const impl of tripleStoreModuleManager.getImplementationNames()) {
const { tripleStoreConfig } = tripleStoreModuleManager.getImplementation(impl);
for (const repo of Object.keys(tripleStoreConfig.repositories)) {
this.logger.log('🗑 Removing triple store repository:', repo);
await tripleStoreModuleManager.deleteRepository(impl, repo);
// Skip if tripleStore module is not defined
if (!config?.modules?.tripleStore) {
continue;
}

cleanupPromises.push((async () => {
try {
const tripleStoreModuleManager = new TripleStoreModuleManager({
config,
logger: this.logger,
});
await tripleStoreModuleManager.initialize();
for (const impl of tripleStoreModuleManager.getImplementationNames()) {
const { tripleStoreConfig } = tripleStoreModuleManager.getImplementation(impl);
for (const repo of Object.keys(tripleStoreConfig.repositories)) {
this.logger.log('🗑 Removing triple store repository:', repo);
await tripleStoreModuleManager.deleteRepository(impl, repo);
}
}
} catch (error) {
// Log but don't fail cleanup if tripleStore cleanup fails
this.logger.warn(`⚠️ Could not clean up tripleStore: ${error.message}`);
}
})());
}

await Promise.all(promises);
await Promise.all(cleanupPromises);
con.end();

this.logger.log('\n✅ Completed scenario:', testCase.pickle.name);
this.logger.log(`📄 Location: ${testCase.gherkinDocument.uri}:${testCase.gherkinDocument.feature.location.line}`);
this.logger.log(`🟢 Status: ${testCase.result.status}`);
this.logger.log(`⏱ Duration: ${testCase.result.duration} milliseconds\n`);
done();
});

AfterAll(async () => {});

process.on('unhandledRejection', () => {
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.abort();
});
Loading
Loading