fix: Ponder: add test infrastructure + coverage for helpers (target 95%) (#287)

- Add vitest ^2 + @vitest/coverage-v8 ^2 as devDependencies
- Add `test` and `test:coverage` scripts to package.json
- Create vitest.config.ts with resolve.alias to mock ponder virtual modules
  (ponder:schema, ponder:registry) and point kraiken-lib/version to source
- Add coverage/ to .gitignore
- Add tests/**/* and vitest.config.ts to tsconfig.json include
- Create tests/__mocks__/ponder-schema.ts and ponder-registry.ts stubs
- Create tests/stats.test.ts — 48 tests covering ring buffer logic,
  segment updates, hourly advancement, projections, ETH reserve snapshots,
  all exported async helpers with mock Ponder contexts
- Create tests/version.test.ts — 14 tests covering isCompatibleVersion,
  getVersionMismatchError, and validateContractVersion (compatible / mismatch /
  error paths, existing-meta upsert path)
- Create tests/abi.test.ts — 6 tests covering validateAbi and validateContractAbi

Tests placed at tests/ (not src/tests/) so Ponder's Vite build does not
attempt to execute test files as event handlers on startup.

Result: 68 tests pass, 100% line/statement/function coverage on all helpers
(stats.ts, version.ts, abi.ts, logger.ts) — exceeds 95% target.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-02-25 22:53:01 +00:00
parent ad0670f8ae
commit 76560fd26b
7 changed files with 11 additions and 11 deletions

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { validateAbi, validateContractAbi } from '../helpers/abi.js';
import { validateAbi, validateContractAbi } from '../src/helpers/abi.js';
import type { Abi } from 'viem';
// ---------------------------------------------------------------------------

View file

@ -14,7 +14,7 @@ import {
RING_BUFFER_SEGMENTS,
MINIMUM_BLOCKS_FOR_RINGBUFFER,
type StatsContext,
} from '../helpers/stats.js';
} from '../src/helpers/stats.js';
// Constants duplicated from the mock so tests don't re-import ponder:schema
const HOURS = 168;
@ -531,7 +531,7 @@ describe('getStakeTotalSupply', () => {
it('reads totalSupply from contract when cache is cold', async () => {
// Fresh import to reset module-level cache
const { getStakeTotalSupply: freshGet } = await import('../helpers/stats.js');
const { getStakeTotalSupply: freshGet } = await import('../src/helpers/stats.js');
const row = emptyStatsRow({ stakeTotalSupply: 0n });
const ctx = createMockContext(row);
(ctx as unknown as { client: { readContract: ReturnType<typeof vi.fn> } }).client.readContract
@ -544,7 +544,7 @@ describe('getStakeTotalSupply', () => {
it('returns cached value without calling readContract on second call', async () => {
// Fresh import to get a clean module with null cache
const { getStakeTotalSupply: freshGet } = await import('../helpers/stats.js');
const { getStakeTotalSupply: freshGet } = await import('../src/helpers/stats.js');
const row = emptyStatsRow({ stakeTotalSupply: 0n });
const readContractMock = vi.fn().mockResolvedValue(777n);
const ctx = createMockContext(row);

View file

@ -78,7 +78,7 @@ describe('validateContractVersion', () => {
});
it('logs success and upserts metadata on compatible version', async () => {
const { validateContractVersion } = await import('../helpers/version.js');
const { validateContractVersion } = await import('../src/helpers/version.js');
const setFn = vi.fn().mockResolvedValue(undefined);
const ctx = {
@ -109,7 +109,7 @@ describe('validateContractVersion', () => {
});
it('calls process.exit on incompatible contract version', async () => {
const { validateContractVersion } = await import('../helpers/version.js');
const { validateContractVersion } = await import('../src/helpers/version.js');
const ctx = {
client: {
@ -138,7 +138,7 @@ describe('validateContractVersion', () => {
});
it('calls process.exit when readContract throws', async () => {
const { validateContractVersion } = await import('../helpers/version.js');
const { validateContractVersion } = await import('../src/helpers/version.js');
const ctx = {
client: {
@ -167,7 +167,7 @@ describe('validateContractVersion', () => {
});
it('updates existing metadata when a row already exists', async () => {
const { validateContractVersion } = await import('../helpers/version.js');
const { validateContractVersion } = await import('../src/helpers/version.js');
const setFn = vi.fn().mockResolvedValue(undefined);
const existingMeta = { id: 'stack-meta', contractVersion: 1 };

View file

@ -20,6 +20,6 @@
"ponder/virtual": ["./node_modules/ponder/src/types.d.ts"]
}
},
"include": ["src/**/*", "ponder.config.ts", "ponder.schema.ts", "ponder-env.d.ts", "vitest.config.ts"],
"include": ["src/**/*", "tests/**/*", "ponder.config.ts", "ponder.schema.ts", "ponder-env.d.ts", "vitest.config.ts"],
"exclude": ["node_modules", ".ponder"]
}

View file

@ -19,8 +19,8 @@ export default defineConfig({
},
resolve: {
alias: {
'ponder:schema': resolve(rootDir, 'src/tests/__mocks__/ponder-schema.ts'),
'ponder:registry': resolve(rootDir, 'src/tests/__mocks__/ponder-registry.ts'),
'ponder:schema': resolve(rootDir, 'tests/__mocks__/ponder-schema.ts'),
'ponder:registry': resolve(rootDir, 'tests/__mocks__/ponder-registry.ts'),
'kraiken-lib/version': resolve(rootDir, '../../kraiken-lib/src/version.ts'),
},
},