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:
commit
444da636ad
2 changed files with 149 additions and 2 deletions
17
kraiken-lib/package-lock.json
generated
17
kraiken-lib/package-lock.json
generated
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
134
services/txnBot/src/service.test.ts
Normal file
134
services/txnBot/src/service.test.ts
Normal 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/);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue