Merge pull request 'fix: txnBot has zero test coverage after deleting recenterAccess.test.ts (#919)' (#930) from fix/issue-919 into master

This commit is contained in:
johba 2026-03-17 20:57:37 +01:00
commit 444da636ad
2 changed files with 149 additions and 2 deletions

View file

@ -226,6 +226,7 @@
"integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
@ -2742,6 +2743,7 @@
"integrity": "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.13.0"
}
@ -2802,6 +2804,7 @@
"integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
@ -3413,6 +3416,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3737,6 +3741,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
@ -4525,6 +4530,7 @@
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -5222,6 +5228,7 @@
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
"license": "MIT",
"peer": true,
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
@ -5304,8 +5311,9 @@
"version": "5.16.0",
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.16.0.tgz",
"integrity": "sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@ -7212,6 +7220,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -8146,8 +8155,9 @@
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -8354,6 +8364,7 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@ -8452,6 +8463,7 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@ -8667,6 +8679,7 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},

View file

@ -0,0 +1,134 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { createTxnBot, type TxnBotDependencies } from './service.js';
import type { BotConfigService } from './services/BotConfigService.js';
import type { BlockchainService } from './services/BlockchainService.js';
import type { GraphQLService } from './services/GraphQLService.js';
const BASE_CONFIG = {
PROVIDER_URL: 'http://localhost:8545',
PRIVATE_KEY: '0xdeadbeef000000000000000000000000000000000000000000000000deadbeef',
LM_CONTRACT_ADDRESS: '0x0000000000000000000000000000000000000001',
STAKE_CONTRACT_ADDRESS: '0x0000000000000000000000000000000000000002',
GRAPHQL_ENDPOINT: 'http://localhost:42069/graphql',
ENVIRONMENT: 'test',
PORT: '3000',
};
function makeTx(hash: string) {
return { hash, wait: (): Promise<null> => Promise.resolve(null) };
}
function makeDeps(
blockchain?: Partial<{
estimateRecenterGas: () => Promise<void>;
recenter: () => Promise<ReturnType<typeof makeTx>>;
}>,
): TxnBotDependencies {
return {
configService: {
getConfig: () => ({ ...BASE_CONFIG }),
getPort: () => '3000',
} as unknown as BotConfigService,
blockchainService: {
estimateRecenterGas: blockchain?.estimateRecenterGas ?? (() => Promise.resolve()),
recenter: blockchain?.recenter ?? (() => Promise.resolve(makeTx('0xabc'))),
checkFunds: () => Promise.resolve('1.0'),
payTax: () => Promise.resolve(makeTx('0xpay')),
} as unknown as BlockchainService,
graphQLService: {
fetchActivePositions: () => Promise.resolve([]),
} as unknown as GraphQLService,
};
}
describe('evaluateRecenterOpportunity', () => {
it('returns canRecenter: true when gas estimation succeeds', async () => {
const bot = createTxnBot(makeDeps());
const result = await bot.evaluateRecenterOpportunity();
assert.equal(result.canRecenter, true);
assert.equal(result.reason, null);
assert.equal(result.error, null);
assert.ok(result.checkedAtMs > 0);
});
it('returns canRecenter: false with extracted revert reason', async () => {
const bot = createTxnBot(
makeDeps({
estimateRecenterGas: () =>
Promise.reject({
shortMessage: 'execution reverted: recenter not needed',
message: 'execution reverted: recenter not needed',
}),
}),
);
const result = await bot.evaluateRecenterOpportunity();
assert.equal(result.canRecenter, false);
assert.equal(result.reason, 'recenter not needed');
assert.equal(result.error, 'execution reverted: recenter not needed');
});
it('returns canRecenter: false with generic message when no revert prefix', async () => {
const bot = createTxnBot(
makeDeps({
estimateRecenterGas: () => Promise.reject({ message: 'connection refused' }),
}),
);
const result = await bot.evaluateRecenterOpportunity();
assert.equal(result.canRecenter, false);
assert.equal(result.reason, 'connection refused');
assert.ok(result.checkedAtMs > 0);
});
});
describe('attemptRecenter', () => {
it('calls recenter() and returns executed: true when eligible', async () => {
let recenterCalled = false;
const bot = createTxnBot(
makeDeps({
recenter: () => {
recenterCalled = true;
return Promise.resolve(makeTx('0xdeadbeef'));
},
}),
);
const result = await bot.attemptRecenter();
assert.equal(result.executed, true);
assert.equal(result.txHash, '0xdeadbeef');
assert.ok(recenterCalled, 'recenter() should have been called');
});
it('skips recenter() and returns executed: false when not eligible', async () => {
let recenterCalled = false;
const bot = createTxnBot(
makeDeps({
estimateRecenterGas: () => Promise.reject({ message: 'recenter not needed' }),
recenter: () => {
recenterCalled = true;
return Promise.resolve(makeTx('0xabc'));
},
}),
);
const result = await bot.attemptRecenter();
assert.equal(result.executed, false);
assert.equal(recenterCalled, false, 'recenter() must not be called when not eligible');
assert.ok(result.message);
});
it('propagates error thrown by recenter() — caught by liquidityLoop', async () => {
const bot = createTxnBot(
makeDeps({
recenter: () => Promise.reject(new Error('tx submission failed')),
}),
);
await assert.rejects(bot.attemptRecenter(), /tx submission failed/);
});
});