-
Notifications
You must be signed in to change notification settings - Fork 49
Add and extend unit tests across SDK packages #313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
71fab4e
3322a5d
02e460f
cf11419
02d84ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { describe, it, expect } from '@jest/globals'; | ||
| import { promises as fs } from 'node:fs'; | ||
| import path from 'node:path'; | ||
|
|
||
| const PACKAGE_ROOT = path.resolve(__dirname, '..'); | ||
| const TEMPLATES_DIR = path.join(PACKAGE_ROOT, 'templates'); | ||
| const EXTENSIONS_DIR = path.join(PACKAGE_ROOT, 'extensions'); | ||
|
|
||
| const EXPECTED_TEMPLATES = ['next-template']; | ||
| const EXPECTED_EXTENSIONS = ['precompiles']; | ||
|
|
||
| describe('Templates', () => { | ||
| it('templates directory exists', async () => { | ||
| const stat = await fs.stat(TEMPLATES_DIR); | ||
| expect(stat.isDirectory()).toBe(true); | ||
| }); | ||
|
|
||
| it.each(EXPECTED_TEMPLATES)('%s template directory exists', async (template) => { | ||
| const templatePath = path.join(TEMPLATES_DIR, template); | ||
| const stat = await fs.stat(templatePath); | ||
| expect(stat.isDirectory()).toBe(true); | ||
| }); | ||
|
|
||
| it.each(EXPECTED_TEMPLATES)('%s template has a valid package.json', async (template) => { | ||
| const pkgPath = path.join(TEMPLATES_DIR, template, 'package.json'); | ||
| const contents = await fs.readFile(pkgPath, 'utf-8'); | ||
| const parsed = JSON.parse(contents); | ||
| expect(typeof parsed.name).toBe('string'); | ||
| expect(parsed.name.trim().length).toBeGreaterThan(0); | ||
| expect(typeof parsed.version).toBe('string'); | ||
| }); | ||
|
|
||
| it.each(EXPECTED_TEMPLATES)('%s template has a tsconfig.json', async (template) => { | ||
| const tsconfigPath = path.join(TEMPLATES_DIR, template, 'tsconfig.json'); | ||
| const stat = await fs.stat(tsconfigPath); | ||
| expect(stat.isFile()).toBe(true); | ||
| }); | ||
|
|
||
| it.each(EXPECTED_TEMPLATES)('%s template has a src/ directory', async (template) => { | ||
| const srcPath = path.join(TEMPLATES_DIR, template, 'src'); | ||
| const stat = await fs.stat(srcPath); | ||
| expect(stat.isDirectory()).toBe(true); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Extensions', () => { | ||
| it('extensions directory exists', async () => { | ||
| const stat = await fs.stat(EXTENSIONS_DIR); | ||
| expect(stat.isDirectory()).toBe(true); | ||
| }); | ||
|
|
||
| it.each(EXPECTED_EXTENSIONS)('%s extension directory exists', async (extension) => { | ||
| const extensionPath = path.join(EXTENSIONS_DIR, extension); | ||
| const stat = await fs.stat(extensionPath); | ||
| expect(stat.isDirectory()).toBe(true); | ||
| }); | ||
|
|
||
| it.each(EXPECTED_EXTENSIONS)('%s extension has a valid package.json', async (extension) => { | ||
| const pkgPath = path.join(EXTENSIONS_DIR, extension, 'package.json'); | ||
| const contents = await fs.readFile(pkgPath, 'utf-8'); | ||
| const parsed = JSON.parse(contents); | ||
| expect(typeof parsed.name).toBe('string'); | ||
| expect(parsed.name.trim().length).toBeGreaterThan(0); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { | ||
| ADDRESS_PRECOMPILE_ABI, | ||
| BANK_PRECOMPILE_ABI, | ||
| DISTRIBUTION_PRECOMPILE_ABI, | ||
| GOVERNANCE_PRECOMPILE_ABI, | ||
| IBC_PRECOMPILE_ABI, | ||
| JSON_PRECOMPILE_ABI, | ||
| ORACLE_PRECOMPILE_ABI, | ||
| POINTER_PRECOMPILE_ABI, | ||
| POINTERVIEW_PRECOMPILE_ABI, | ||
| SOLO_PRECOMPILE_ABI, | ||
| STAKING_PRECOMPILE_ABI, | ||
| WASM_PRECOMPILE_ABI | ||
| } from '../index'; | ||
|
|
||
| type AbiEntry = { type: string; name?: string; inputs?: readonly unknown[]; outputs?: readonly unknown[]; stateMutability?: string }; | ||
| type Abi = readonly AbiEntry[]; | ||
|
|
||
| function getFunctionNames(abi: Abi): string[] { | ||
| return abi.filter((entry) => entry.type === 'function').map((entry) => entry.name!); | ||
| } | ||
|
|
||
| function getFunctions(abi: Abi): AbiEntry[] { | ||
| return abi.filter((entry) => entry.type === 'function'); | ||
| } | ||
|
|
||
| const PRECOMPILE_ABIS: [string, Abi, string[]][] = [ | ||
| ['ADDRESS', ADDRESS_PRECOMPILE_ABI, ['getSeiAddr', 'getEvmAddr', 'associate', 'associatePubKey']], | ||
| ['BANK', BANK_PRECOMPILE_ABI, ['send', 'sendNative', 'balance', 'all_balances', 'supply', 'decimals', 'name', 'symbol']], | ||
| ['DISTRIBUTION', DISTRIBUTION_PRECOMPILE_ABI, ['setWithdrawAddress', 'withdrawDelegationRewards', 'withdrawMultipleDelegationRewards', 'rewards']], | ||
| ['GOVERNANCE', GOVERNANCE_PRECOMPILE_ABI, ['vote', 'deposit']], | ||
| ['IBC', IBC_PRECOMPILE_ABI, ['transfer']], | ||
| ['JSON', JSON_PRECOMPILE_ABI, ['extractAsBytes', 'extractAsBytesList']], | ||
| ['ORACLE', ORACLE_PRECOMPILE_ABI, ['getExchangeRates', 'getOracleTwaps']], | ||
| ['POINTER', POINTER_PRECOMPILE_ABI, ['addCW20Pointer', 'addCW721Pointer', 'addNativePointer']], | ||
| ['POINTERVIEW', POINTERVIEW_PRECOMPILE_ABI, ['getCW20Pointer', 'getCW721Pointer']], | ||
| ['STAKING', STAKING_PRECOMPILE_ABI, ['delegate', 'undelegate', 'redelegate', 'delegation']], | ||
| ['WASM', WASM_PRECOMPILE_ABI, ['execute', 'execute_batch']] | ||
| ]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SOLO precompile omitted from function name testsLow Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit 02d84ca. Configure here. |
||
|
|
||
| describe('Precompile ABIs — function names', () => { | ||
| it.each(PRECOMPILE_ABIS)('%s ABI contains all expected function names', (_name, abi, expectedFunctions) => { | ||
| const actualFunctions = getFunctionNames(abi as Abi); | ||
| for (const fn of expectedFunctions) { | ||
| expect(actualFunctions).toContain(fn); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('Precompile ABIs — function entry structure', () => { | ||
| it.each(PRECOMPILE_ABIS)('%s ABI functions each have inputs, outputs, and stateMutability', (_name, abi) => { | ||
| const functions = getFunctions(abi as Abi); | ||
| expect(functions.length).toBeGreaterThan(0); | ||
|
|
||
| for (const fn of functions) { | ||
| expect(Array.isArray(fn.inputs)).toBe(true); | ||
| expect(Array.isArray(fn.outputs)).toBe(true); | ||
| expect(typeof fn.stateMutability).toBe('string'); | ||
| expect(['view', 'nonpayable', 'payable', 'pure']).toContain(fn.stateMutability); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('Precompile ABIs — top-level structure', () => { | ||
| const ALL_ABIS: [string, Abi][] = [ | ||
| ['ADDRESS', ADDRESS_PRECOMPILE_ABI], | ||
| ['BANK', BANK_PRECOMPILE_ABI], | ||
| ['DISTRIBUTION', DISTRIBUTION_PRECOMPILE_ABI], | ||
| ['GOVERNANCE', GOVERNANCE_PRECOMPILE_ABI], | ||
| ['IBC', IBC_PRECOMPILE_ABI], | ||
| ['JSON', JSON_PRECOMPILE_ABI], | ||
| ['ORACLE', ORACLE_PRECOMPILE_ABI], | ||
| ['POINTER', POINTER_PRECOMPILE_ABI], | ||
| ['POINTERVIEW', POINTERVIEW_PRECOMPILE_ABI], | ||
| ['SOLO', SOLO_PRECOMPILE_ABI], | ||
| ['STAKING', STAKING_PRECOMPILE_ABI], | ||
| ['WASM', WASM_PRECOMPILE_ABI] | ||
| ]; | ||
|
|
||
| it.each(ALL_ABIS)('%s ABI is a non-empty array', (_name, abi) => { | ||
| expect(Array.isArray(abi)).toBe(true); | ||
| expect((abi as Abi).length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it.each(ALL_ABIS)('%s ABI entries each have a type field', (_name, abi) => { | ||
| for (const entry of abi as Abi) { | ||
| expect(typeof entry.type).toBe('string'); | ||
| expect(entry.type.length).toBeGreaterThan(0); | ||
| } | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { | ||
| ADDRESS_PRECOMPILE_ADDRESS, | ||
| BANK_PRECOMPILE_ADDRESS, | ||
| DISTRIBUTION_PRECOMPILE_ADDRESS, | ||
| GOVERNANCE_PRECOMPILE_ADDRESS, | ||
| IBC_PRECOMPILE_ADDRESS, | ||
| JSON_PRECOMPILE_ADDRESS, | ||
| ORACLE_PRECOMPILE_ADDRESS, | ||
| POINTER_PRECOMPILE_ADDRESS, | ||
| POINTERVIEW_PRECOMPILE_ADDRESS, | ||
| SOLO_PRECOMPILE_ADDRESS, | ||
| STAKING_PRECOMPILE_ADDRESS, | ||
| WASM_PRECOMPILE_ADDRESS | ||
| } from '../index'; | ||
|
|
||
| const PRECOMPILE_ADDRESSES: [string, string][] = [ | ||
| ['ADDRESS', ADDRESS_PRECOMPILE_ADDRESS], | ||
| ['BANK', BANK_PRECOMPILE_ADDRESS], | ||
| ['DISTRIBUTION', DISTRIBUTION_PRECOMPILE_ADDRESS], | ||
| ['GOVERNANCE', GOVERNANCE_PRECOMPILE_ADDRESS], | ||
| ['IBC', IBC_PRECOMPILE_ADDRESS], | ||
| ['JSON', JSON_PRECOMPILE_ADDRESS], | ||
| ['ORACLE', ORACLE_PRECOMPILE_ADDRESS], | ||
| ['POINTER', POINTER_PRECOMPILE_ADDRESS], | ||
| ['POINTERVIEW', POINTERVIEW_PRECOMPILE_ADDRESS], | ||
| ['SOLO', SOLO_PRECOMPILE_ADDRESS], | ||
| ['STAKING', STAKING_PRECOMPILE_ADDRESS], | ||
| ['WASM', WASM_PRECOMPILE_ADDRESS] | ||
| ]; | ||
|
|
||
| /** Validates an ERC-55 checksummed Ethereum address: 0x + exactly 40 hex characters. */ | ||
| function isValidEthAddress(address: string): boolean { | ||
| return /^0x[0-9a-fA-F]{40}$/.test(address); | ||
| } | ||
|
|
||
| describe('Precompile addresses', () => { | ||
| it.each(PRECOMPILE_ADDRESSES)('%s address is a valid 42-character Ethereum address', (_name, address) => { | ||
| expect(typeof address).toBe('string'); | ||
| expect(isValidEthAddress(address)).toBe(true); | ||
| }); | ||
|
|
||
| it('all precompile addresses are unique', () => { | ||
| const addresses = PRECOMPILE_ADDRESSES.map(([, addr]) => addr.toLowerCase()); | ||
| const unique = new Set(addresses); | ||
| expect(unique.size).toBe(addresses.length); | ||
| }); | ||
|
|
||
| it('all precompile addresses start with 0x000000000000000000000000000000000000', () => { | ||
| // Sei precompiles live in the reserved 0x1000–0x10FF range | ||
| for (const [, address] of PRECOMPILE_ADDRESSES) { | ||
| expect(address.toLowerCase()).toMatch(/^0x0{36}/); | ||
| } | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,4 +32,19 @@ describe('GasInfo Tests', () => { | |
| expect(pacific1.min_gas_price).toBeGreaterThanOrEqual(0.01); | ||
| expect(pacific1.module_adjustments.dex.sudo_gas_price).toBeLessThanOrEqual(0.02); | ||
| }); | ||
|
|
||
| it('all networks have a positive min_gas_price', () => { | ||
| for (const [network, info] of Object.entries(GAS_INFO)) { | ||
| expect(info.min_gas_price).toBeGreaterThan(0); | ||
| if (info.min_gas_price <= 0) { | ||
| throw new Error(`Network ${network} has non-positive min_gas_price: ${info.min_gas_price}`); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unreachable throw statements after Jest expect assertionsLow Severity Several tests follow an Additional Locations (2)Reviewed by Cursor Bugbot for commit 02d84ca. Configure here. |
||
| } | ||
| }); | ||
|
|
||
| it('all networks use usei as the fee denom', () => { | ||
| for (const info of Object.values(GAS_INFO)) { | ||
| expect(info.denom).toBe('usei'); | ||
| } | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { config } from '../config'; | ||
|
|
||
| describe('sei-global-wallet config', () => { | ||
| it('walletName is a non-empty string', () => { | ||
| expect(typeof config.walletName).toBe('string'); | ||
| expect(config.walletName.trim().length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('walletUrl starts with https://', () => { | ||
| expect(typeof config.walletUrl).toBe('string'); | ||
| expect(config.walletUrl).toMatch(/^https:\/\//); | ||
| }); | ||
|
|
||
| it('environmentId is a non-empty string', () => { | ||
| expect(typeof config.environmentId).toBe('string'); | ||
| expect(config.environmentId.trim().length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('eip6963.rdns matches the io.sei.* pattern', () => { | ||
| expect(typeof config.eip6963.rdns).toBe('string'); | ||
| expect(config.eip6963.rdns).toMatch(/^io\.sei\./); | ||
| }); | ||
|
|
||
| it('walletIcon is a non-empty string', () => { | ||
| expect(typeof config.walletIcon).toBe('string'); | ||
| expect((config.walletIcon as string).trim().length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('walletIcon is a valid data URI', () => { | ||
| expect(config.walletIcon as string).toMatch(/^data:/); | ||
| }); | ||
| }); |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
POINTERVIEW test omits
getNativePointerfrom expected functionsLow Severity
The
POINTERVIEWentry inPRECOMPILE_ABISonly lists['getCW20Pointer', 'getCW721Pointer']as expected functions, butPOINTERVIEW_PRECOMPILE_ABIactually contains a third function,getNativePointer. The test only verifies that listed functions are present (not that all functions are listed), so this silently skips validation ofgetNativePointer.Reviewed by Cursor Bugbot for commit 02d84ca. Configure here.