Skip to content

Commit f0c8fe8

Browse files
gisk0claude
authored andcommitted
fix(test): make self-heal tests hermetic and failure-safe
- Explicitly mock null referenceRateFeedID during deploy to avoid real RPC calls in CI - Move mock cleanup to afterEach so state doesn't leak on assertion failure Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 991879a commit f0c8fe8

File tree

1 file changed

+131
-132
lines changed

1 file changed

+131
-132
lines changed

indexer-envio/test/Test.ts

Lines changed: 131 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,138 +1253,137 @@ describe("Envio Celo indexer handlers", () => {
12531253
// Self-heal: backfill referenceRateFeedID on subsequent events
12541254
// ---------------------------------------------------------------------------
12551255

1256-
it("self-heals empty referenceRateFeedID on next Swap event", async function () {
1257-
this.timeout(10_000);
1258-
1259-
const POOL_ADDR = "0x00000000000000000000000000000000000000dd";
1260-
const HEALED_FEED = "0xf47172ce00522cc7db02109634a92ce866a15fcc";
1261-
const HEALED_EXPIRY = 3720n;
1262-
const CHAIN_ID = 42220;
1263-
1264-
// 1. Deploy pool (referenceRateFeedID will be "" because no mock is set
1265-
// for the RPC call during deployment — simulating the transient failure)
1266-
let mockDb = MockDb.createMockDb();
1267-
1268-
const deployEvent = FPMMFactory.FPMMDeployed.createMockEvent({
1269-
token0: "0x0000000000000000000000000000000000000003",
1270-
token1: "0x0000000000000000000000000000000000000004",
1271-
fpmmProxy: POOL_ADDR,
1272-
fpmmImplementation: "0x00000000000000000000000000000000000000bc",
1273-
mockEventData: {
1274-
chainId: CHAIN_ID,
1275-
logIndex: 10,
1276-
srcAddress: "0x00000000000000000000000000000000000000cc",
1277-
block: { number: 100, timestamp: 1_700_000_000 },
1278-
},
1256+
describe("self-heal referenceRateFeedID", () => {
1257+
afterEach(() => {
1258+
_clearMockRateFeedIDs();
1259+
_clearMockReportExpiry();
1260+
});
1261+
1262+
it("self-heals empty referenceRateFeedID on next Swap event", async function () {
1263+
this.timeout(10_000);
1264+
1265+
const POOL_ADDR = "0x00000000000000000000000000000000000000dd";
1266+
const HEALED_FEED = "0xf47172ce00522cc7db02109634a92ce866a15fcc";
1267+
const HEALED_EXPIRY = 3720n;
1268+
const CHAIN_ID = 42220;
1269+
1270+
// 1. Deploy pool — mock null to explicitly simulate transient RPC failure
1271+
_setMockRateFeedID(CHAIN_ID, POOL_ADDR, null);
1272+
let mockDb = MockDb.createMockDb();
1273+
1274+
const deployEvent = FPMMFactory.FPMMDeployed.createMockEvent({
1275+
token0: "0x0000000000000000000000000000000000000003",
1276+
token1: "0x0000000000000000000000000000000000000004",
1277+
fpmmProxy: POOL_ADDR,
1278+
fpmmImplementation: "0x00000000000000000000000000000000000000bc",
1279+
mockEventData: {
1280+
chainId: CHAIN_ID,
1281+
logIndex: 10,
1282+
srcAddress: "0x00000000000000000000000000000000000000cc",
1283+
block: { number: 100, timestamp: 1_700_000_000 },
1284+
},
1285+
});
1286+
mockDb = await FPMMFactory.FPMMDeployed.processEvent({
1287+
event: deployEvent,
1288+
mockDb,
1289+
});
1290+
1291+
// Verify the pool was created with empty referenceRateFeedID
1292+
const poolBefore = mockDb.entities.Pool.get(POOL_ADDR) as PoolEntity;
1293+
assert.ok(poolBefore, "Pool must exist after deploy");
1294+
assert.equal(
1295+
poolBefore.referenceRateFeedID,
1296+
"",
1297+
"referenceRateFeedID should be empty after failed initial fetch",
1298+
);
1299+
assert.equal(
1300+
poolBefore.oracleExpiry,
1301+
0n,
1302+
"oracleExpiry should be 0 when referenceRateFeedID is empty",
1303+
);
1304+
1305+
// 2. Set up mocks so the self-heal RPC calls succeed
1306+
_setMockRateFeedID(CHAIN_ID, POOL_ADDR, HEALED_FEED);
1307+
_setMockReportExpiry(CHAIN_ID, HEALED_FEED, HEALED_EXPIRY);
1308+
1309+
// 3. Process a Swap event — should trigger self-heal
1310+
const swapEvent = FPMM.Swap.createMockEvent({
1311+
sender: "0x0000000000000000000000000000000000000099",
1312+
to: "0x0000000000000000000000000000000000000098",
1313+
amount0In: 1000n,
1314+
amount1In: 0n,
1315+
amount0Out: 0n,
1316+
amount1Out: 990n,
1317+
mockEventData: {
1318+
chainId: CHAIN_ID,
1319+
logIndex: 20,
1320+
srcAddress: POOL_ADDR,
1321+
block: { number: 200, timestamp: 1_700_001_000 },
1322+
},
1323+
});
1324+
mockDb = await FPMM.Swap.processEvent({ event: swapEvent, mockDb });
1325+
1326+
// 4. Verify self-heal populated the fields
1327+
const poolAfter = mockDb.entities.Pool.get(POOL_ADDR) as PoolEntity;
1328+
assert.ok(poolAfter, "Pool must exist after Swap");
1329+
assert.equal(
1330+
poolAfter.referenceRateFeedID,
1331+
HEALED_FEED,
1332+
"referenceRateFeedID should be healed after Swap event",
1333+
);
1334+
assert.equal(
1335+
poolAfter.oracleExpiry,
1336+
HEALED_EXPIRY,
1337+
"oracleExpiry should be healed after Swap event",
1338+
);
1339+
});
1340+
1341+
it("does NOT self-heal when referenceRateFeedID is already populated", async function () {
1342+
this.timeout(10_000);
1343+
1344+
const POOL_ADDR = "0x00000000000000000000000000000000000000ee";
1345+
const EXISTING_FEED = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
1346+
const CHAIN_ID = 42220;
1347+
1348+
// 1. Seed pool with an existing referenceRateFeedID
1349+
let mockDb = await seedPoolWithFeed(MockDb.createMockDb(), {
1350+
poolId: POOL_ADDR,
1351+
feedId: EXISTING_FEED,
1352+
oracleExpiry: 600n,
1353+
});
1354+
1355+
// 2. Set up a different mock — should NOT be used because feed is already set
1356+
_setMockRateFeedID(
1357+
CHAIN_ID,
1358+
POOL_ADDR,
1359+
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1360+
);
1361+
1362+
// 3. Process a Swap event
1363+
const swapEvent = FPMM.Swap.createMockEvent({
1364+
sender: "0x0000000000000000000000000000000000000099",
1365+
to: "0x0000000000000000000000000000000000000098",
1366+
amount0In: 500n,
1367+
amount1In: 0n,
1368+
amount0Out: 0n,
1369+
amount1Out: 495n,
1370+
mockEventData: {
1371+
chainId: CHAIN_ID,
1372+
logIndex: 30,
1373+
srcAddress: POOL_ADDR,
1374+
block: { number: 400, timestamp: 1_700_002_000 },
1375+
},
1376+
});
1377+
mockDb = await FPMM.Swap.processEvent({ event: swapEvent, mockDb });
1378+
1379+
// 4. Verify feed was NOT changed
1380+
const pool = mockDb.entities.Pool.get(POOL_ADDR) as PoolEntity;
1381+
assert.ok(pool, "Pool must exist after Swap");
1382+
assert.equal(
1383+
pool.referenceRateFeedID,
1384+
EXISTING_FEED,
1385+
"referenceRateFeedID should remain unchanged when already populated",
1386+
);
12791387
});
1280-
mockDb = await FPMMFactory.FPMMDeployed.processEvent({
1281-
event: deployEvent,
1282-
mockDb,
1283-
});
1284-
1285-
// Verify the pool was created with empty referenceRateFeedID
1286-
const poolBefore = mockDb.entities.Pool.get(POOL_ADDR) as PoolEntity;
1287-
assert.ok(poolBefore, "Pool must exist after deploy");
1288-
assert.equal(
1289-
poolBefore.referenceRateFeedID,
1290-
"",
1291-
"referenceRateFeedID should be empty after failed initial fetch",
1292-
);
1293-
assert.equal(
1294-
poolBefore.oracleExpiry,
1295-
0n,
1296-
"oracleExpiry should be 0 when referenceRateFeedID is empty",
1297-
);
1298-
1299-
// 2. Set up mocks so the self-heal RPC calls succeed
1300-
_setMockRateFeedID(CHAIN_ID, POOL_ADDR, HEALED_FEED);
1301-
_setMockReportExpiry(CHAIN_ID, HEALED_FEED, HEALED_EXPIRY);
1302-
1303-
// 3. Process a Swap event — should trigger self-heal
1304-
const swapEvent = FPMM.Swap.createMockEvent({
1305-
sender: "0x0000000000000000000000000000000000000099",
1306-
to: "0x0000000000000000000000000000000000000098",
1307-
amount0In: 1000n,
1308-
amount1In: 0n,
1309-
amount0Out: 0n,
1310-
amount1Out: 990n,
1311-
mockEventData: {
1312-
chainId: CHAIN_ID,
1313-
logIndex: 20,
1314-
srcAddress: POOL_ADDR,
1315-
block: { number: 200, timestamp: 1_700_001_000 },
1316-
},
1317-
});
1318-
mockDb = await FPMM.Swap.processEvent({ event: swapEvent, mockDb });
1319-
1320-
// 4. Verify self-heal populated the fields
1321-
const poolAfter = mockDb.entities.Pool.get(POOL_ADDR) as PoolEntity;
1322-
assert.ok(poolAfter, "Pool must exist after Swap");
1323-
assert.equal(
1324-
poolAfter.referenceRateFeedID,
1325-
HEALED_FEED,
1326-
"referenceRateFeedID should be healed after Swap event",
1327-
);
1328-
assert.equal(
1329-
poolAfter.oracleExpiry,
1330-
HEALED_EXPIRY,
1331-
"oracleExpiry should be healed after Swap event",
1332-
);
1333-
1334-
// Cleanup
1335-
_clearMockRateFeedIDs();
1336-
_clearMockReportExpiry();
1337-
});
1338-
1339-
it("does NOT self-heal when referenceRateFeedID is already populated", async function () {
1340-
this.timeout(10_000);
1341-
1342-
const POOL_ADDR = "0x00000000000000000000000000000000000000ee";
1343-
const EXISTING_FEED = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
1344-
const CHAIN_ID = 42220;
1345-
1346-
// 1. Seed pool with an existing referenceRateFeedID
1347-
let mockDb = await seedPoolWithFeed(MockDb.createMockDb(), {
1348-
poolId: POOL_ADDR,
1349-
feedId: EXISTING_FEED,
1350-
oracleExpiry: 600n,
1351-
});
1352-
1353-
// 2. Set up a different mock — should NOT be used because feed is already set
1354-
_setMockRateFeedID(
1355-
CHAIN_ID,
1356-
POOL_ADDR,
1357-
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1358-
);
1359-
1360-
// 3. Process a Swap event
1361-
const swapEvent = FPMM.Swap.createMockEvent({
1362-
sender: "0x0000000000000000000000000000000000000099",
1363-
to: "0x0000000000000000000000000000000000000098",
1364-
amount0In: 500n,
1365-
amount1In: 0n,
1366-
amount0Out: 0n,
1367-
amount1Out: 495n,
1368-
mockEventData: {
1369-
chainId: CHAIN_ID,
1370-
logIndex: 30,
1371-
srcAddress: POOL_ADDR,
1372-
block: { number: 400, timestamp: 1_700_002_000 },
1373-
},
1374-
});
1375-
mockDb = await FPMM.Swap.processEvent({ event: swapEvent, mockDb });
1376-
1377-
// 4. Verify feed was NOT changed
1378-
const pool = mockDb.entities.Pool.get(POOL_ADDR) as PoolEntity;
1379-
assert.ok(pool, "Pool must exist after Swap");
1380-
assert.equal(
1381-
pool.referenceRateFeedID,
1382-
EXISTING_FEED,
1383-
"referenceRateFeedID should remain unchanged when already populated",
1384-
);
1385-
1386-
// Cleanup
1387-
_clearMockRateFeedIDs();
1388-
_clearMockReportExpiry();
13891388
});
13901389
});

0 commit comments

Comments
 (0)