import { describe, it, expect, vi, afterEach } from 'vitest'; import { isCompatibleVersion, getVersionMismatchError, KRAIKEN_LIB_VERSION, COMPATIBLE_CONTRACT_VERSIONS, } from 'kraiken-lib/version'; // --------------------------------------------------------------------------- // isCompatibleVersion (from kraiken-lib/version, aliased to source) // --------------------------------------------------------------------------- describe('isCompatibleVersion', () => { it('returns true for each version in COMPATIBLE_CONTRACT_VERSIONS', () => { for (const v of COMPATIBLE_CONTRACT_VERSIONS) { expect(isCompatibleVersion(v)).toBe(true); } }); it('returns false for version 0', () => { expect(isCompatibleVersion(0)).toBe(false); }); it('returns false for a future version not yet listed', () => { expect(isCompatibleVersion(9999)).toBe(false); }); it('returns false for negative version', () => { expect(isCompatibleVersion(-1)).toBe(false); }); }); // --------------------------------------------------------------------------- // getVersionMismatchError (from kraiken-lib/version) // --------------------------------------------------------------------------- describe('getVersionMismatchError', () => { it('includes the bad contract version in the output (ponder context)', () => { const msg = getVersionMismatchError(99, 'ponder'); expect(msg).toContain('99'); }); it('includes the library version in the output', () => { const msg = getVersionMismatchError(99, 'ponder'); expect(msg).toContain(String(KRAIKEN_LIB_VERSION)); }); it('includes ponder-specific remediation steps', () => { const msg = getVersionMismatchError(99, 'ponder'); expect(msg).toContain('COMPATIBLE_CONTRACT_VERSIONS'); }); it('includes frontend-specific remediation steps', () => { const msg = getVersionMismatchError(99, 'frontend'); expect(msg).toContain('administrator'); }); it('formats compatible versions list', () => { const msg = getVersionMismatchError(99, 'ponder'); for (const v of COMPATIBLE_CONTRACT_VERSIONS) { expect(msg).toContain(String(v)); } }); it('returns a multi-line string (box drawing)', () => { const msg = getVersionMismatchError(99, 'ponder'); expect(msg.split('\n').length).toBeGreaterThan(3); }); }); // --------------------------------------------------------------------------- // validateContractVersion (helpers/version.ts) — mock the Ponder context // --------------------------------------------------------------------------- describe('validateContractVersion', () => { afterEach(() => { vi.restoreAllMocks(); }); it('logs success and upserts metadata on compatible version', async () => { const { validateContractVersion } = await import('../helpers/version.js'); const setFn = vi.fn().mockResolvedValue(undefined); const ctx = { client: { readContract: vi.fn().mockResolvedValue(KRAIKEN_LIB_VERSION), }, contracts: { Kraiken: { abi: [], address: '0x0001' as `0x${string}` }, }, db: { find: vi.fn().mockResolvedValue(null), insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }), update: vi.fn().mockReturnValue({ set: setFn }), }, logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), }, }; const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never); await validateContractVersion(ctx as unknown as import('ponder:registry').Context); expect(exitSpy).not.toHaveBeenCalled(); expect(ctx.logger.info).toHaveBeenCalled(); }); it('calls process.exit on incompatible contract version', async () => { const { validateContractVersion } = await import('../helpers/version.js'); const ctx = { client: { readContract: vi.fn().mockResolvedValue(9999n), }, contracts: { Kraiken: { abi: [], address: '0x0001' as `0x${string}` }, }, db: { find: vi.fn().mockResolvedValue(null), insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }), update: vi.fn().mockReturnValue({ set: vi.fn().mockResolvedValue(undefined) }), }, logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), }, }; const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never); await validateContractVersion(ctx as unknown as import('ponder:registry').Context); expect(exitSpy).toHaveBeenCalledWith(1); }); it('calls process.exit when readContract throws', async () => { const { validateContractVersion } = await import('../helpers/version.js'); const ctx = { client: { readContract: vi.fn().mockRejectedValue(new Error('network error')), }, contracts: { Kraiken: { abi: [], address: '0x0001' as `0x${string}` }, }, db: { find: vi.fn().mockResolvedValue(null), insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }), update: vi.fn().mockReturnValue({ set: vi.fn().mockResolvedValue(undefined) }), }, logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), }, }; const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never); await validateContractVersion(ctx as unknown as import('ponder:registry').Context); expect(exitSpy).toHaveBeenCalledWith(1); }); it('updates existing metadata when a row already exists', async () => { const { validateContractVersion } = await import('../helpers/version.js'); const setFn = vi.fn().mockResolvedValue(undefined); const existingMeta = { id: 'stack-meta', contractVersion: 1 }; const ctx = { client: { readContract: vi.fn().mockResolvedValue(KRAIKEN_LIB_VERSION), }, contracts: { Kraiken: { abi: [], address: '0x0001' as `0x${string}` }, }, db: { find: vi.fn().mockResolvedValue(existingMeta), insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }), update: vi.fn().mockReturnValue({ set: setFn }), }, logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), }, }; const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never); await validateContractVersion(ctx as unknown as import('ponder:registry').Context); expect(exitSpy).not.toHaveBeenCalled(); expect(ctx.db.update).toHaveBeenCalled(); expect(ctx.db.insert).not.toHaveBeenCalled(); }); });