一键导入
wdk-review-tests
Review WDK test suites for compliance with established testing conventions (assertions, mock boundaries, isolation, coverage, naming, structure).
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Review WDK test suites for compliance with established testing conventions (assertions, mock boundaries, isolation, coverage, naming, structure).
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Code of conduct that contributors — including AI coding assistants — MUST follow when writing, modifying, or reviewing code for any Tether Wallet Development Kit (WDK) module (wallets, protocols, core). Covers project workflow (atomic single-feature pull requests, design-before-implementation specs), code quality (avoid code smells, never violate S.O.L.I.D. principles, document every public/protected component, use narrow types, avoid defensive programming, mark every thrown error with @throws), and testing (arrange-act-assert, test only the public API, assert real values not patterns, define jest mocks at the top level, mock all external-service dependencies).
Review WDK source files for compliance with established Types & JSDoc conventions (JSDoc format, type annotations, import/export rules, .d.ts alignment).
Tether Wallet Development Kit (WDK) for building non-custodial multi-chain wallets. Use when working with @tetherto/wdk-core, wallet modules (wdk-wallet-btc, wdk-wallet-evm, wdk-wallet-evm-erc-4337, wdk-wallet-solana, wdk-wallet-spark, wdk-wallet-ton, wdk-wallet-tron, ton-gasless, tron-gasfree), and protocol modules including swap (wdk-protocol-swap-velora-evm, bridge (wdk-protocol-bridge-usdt0-evm), lending (wdk-protocol-lending-aave-evm), and fiat (wdk-protocol-fiat-moonpay). Covers wallet creation, transactions, token transfers, DEX swaps, cross-chain bridges, DeFi lending/borrowing, and fiat on/off ramps.
| name | wdk-review-tests |
| description | Review WDK test suites for compliance with established testing conventions (assertions, mock boundaries, isolation, coverage, naming, structure). |
| user-invocable | true |
| argument-hint | [file or directory path to test files] |
Review the test files at the provided path (or the current project's test directory if none given). Apply all rules below.
For each violation found, report:
Group findings by file. After all files are reviewed, provide a summary with total violation counts per rule.
You are reviewing test files for a WDK (Wallet Development Kit) module. Apply the rules below to identify violations and suggest fixes.
The rules are repository-agnostic. No WDK module is a "golden reference" — apply every rule to every module equally. If existing code conflicts with a rule, the code is the candidate for change, not the rule.
Every assertion must compare against a known, pre-computed concrete value. Flag any assertion that checks a type, pattern, range, or mere existence.
Violation:
expect(typeof result).toBe('string')
expect(fee).toBeGreaterThan(0)
expect(result).toBeDefined()
expect(result).not.toBe(null)
expect(result).toMatch(/^0x[a-f0-9]+$/)
Correct:
expect(result).toBe(EXPECTED_HASH)
expect(fee).toBe(202_000n)
If the test mocks all external dependencies, the return value is deterministic — compute and hardcode it.
Exception: toBeUndefined() is acceptable — it asserts a concrete value (undefined) and is functionally equivalent to toBe(undefined). Do not flag it.
Every mocked dependency must be asserted with toHaveBeenCalledWith using exact expected arguments. toHaveBeenCalled (without arguments) is insufficient. Limit expect.any(), expect.objectContaining(), and expect.arrayContaining() to cases where the concrete value genuinely cannot be determined.
Violation:
expect(mockClient.getBalance).toHaveBeenCalled()
Correct:
expect(mockClient.getBalance).toHaveBeenCalledWith(EXPECTED_ADDRESS)
Exception: toHaveBeenCalled() without argument verification is acceptable when the method takes no arguments — toHaveBeenCalledWith() adds nothing there.
expect(mockClient.disconnect).toHaveBeenCalled() // disconnect() takes no args — OK
Each unit test must exercise exactly one method of the system under test. A test that calls sign() and then passes the result to verify() is an integration test. Hardcode the expected signature instead.
Violation:
test('should sign and verify', () => {
const sig = account.sign(MESSAGE)
expect(account.verify(MESSAGE, sig)).toBe(true) // exercises two units
})
Correct:
test('should sign a message', () => {
expect(account.sign(MESSAGE)).toBe(EXPECTED_SIGNATURE)
})
Don't write tests for internal/private modules. Don't access private properties (prefixed with _). Internal components are tested indirectly through the public API.
Violation:
account._tronWeb = mockTronWeb
expect(account._account.privateKey).toBe(null)
Correct: use jest.unstable_mockModule to inject mocks via the module system instead of assigning private properties.
If a test file targets an internal utility (e.g., src/memory-safe/, src/multicall.js), flag it for removal.
When testing that a method throws, always include the expected message. Bare .toThrow() doesn't verify the right error was thrown.
Violation:
await expect(account.send(opts)).rejects.toThrow()
Correct:
await expect(account.send(opts)).rejects.toThrow('Insufficient balance')
await expect(account.send(opts)).rejects.toThrow(/Insufficient/) // regex for long messages
When a method returns an object (e.g., { hash, fee, status, to, value }), assert every relevant field — not just status.
Violation:
const result = await account.sendTransaction(opts)
expect(result.status).toBe('success') // missing hash, fee
Correct:
const { hash, fee, status } = await account.sendTransaction(opts)
expect(hash).toBe(EXPECTED_HASH)
expect(fee).toBe(EXPECTED_FEE)
expect(status).toBe('success')
toBe for primitives, toEqual only for objects/arraysFor primitives (strings, numbers, bigints, booleans), use toBe (strict equality). Reserve toEqual for deep comparisons of objects and arrays.
Test hooks (beforeAll, beforeEach) must not call methods from the class being tested to set up state. Use hardcoded expected values or external tools (bitcoin-cli, hardhat RPC, ethers provider).
Violation:
beforeEach(async () => {
address = await account.getAddress() // calling the SUT
})
Correct:
const ADDRESS = '0x1234...' // hardcoded known value
Only mock methods that interact with external services (network, I/O, blockchain nodes). Pure functions, static utility methods, and local computations must use their real implementations.
Violation: mocking TronWeb.address.toHex, decodeSparkAddress, or ethers.parseEther — these are pure functions.
Correct: mock provider.getBalance, client.sendTransaction, fetch — these interact with external systems.
When using jest.unstable_mockModule, spread the real module exports and override only what needs mocking:
import * as realModule from '#libs/spark-sdk'
jest.unstable_mockModule('#libs/spark-sdk', () => ({
...realModule,
SparkClient: { create: jest.fn().mockReturnValue(mockClient) }
}))
Every new public method added in a PR must have corresponding unit tests. Flag any new method in source code that lacks coverage.
When a method accepts optional parameters (limit, offset, direction, confirmationTarget, etc.), there must be test cases covering those variants. Edge cases like null returns and boundary values must also be covered.
DUMMY_ prefix + SCREAMING_CASE — DUMMY_BALANCE, DUMMY_TX_HASH, DUMMY_BALANCE_MAP.SCREAMING_CASE — OPTIONS, TRANSACTION.dummy- prefix (not mock-) — 'https://dummy-provider.url/'.Test fixtures must use realistic values that match actual SDK types. Property names must match the real type definitions.
Violation: using { address: '0x...' } when the real type defines { depositAddress: '0x...' }.
Use realistic BIP-39 seed phrases, not trivially repeated words:
// BAD
const SEED = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
// GOOD
const SEED = 'cook voyage document eight skate token alien guide drink uncle term abuse'
beforeAll — expensive shared setup only (server startup, heavy initialization).beforeEach — per-test state (account creation, blockchain state reset, mock configuration).afterEach — cleanup (dispose, clearMocks if needed).afterAll — expensive shared teardown (server shutdown).Derive accounts at the beginning of each test when possible, not in shared hooks.
Define mock function references at the top level. Configure return values inside individual tests:
const getBalanceMock = jest.fn()
jest.unstable_mockModule('tronweb', () => ({
default: jest.fn(() => ({ trx: { getBalance: getBalanceMock } }))
}))
test('should return balance', async () => {
getBalanceMock.mockResolvedValue(DUMMY_BALANCE)
// ...
})
'should close and clean up connections', not 'should call wallet.cleanupConnections'.describe uses the module name: describe('@wdk/wallet-evm', () => { ... }).tests/ (not test/).tests/wallet-account-*.test.js, tests/wallet-manager-*.test.js.tests/integration/module.test.js.tests/integration/helpers.js.describe: declare at the describe scope.test() block.beforeEach/afterEach for per-test setup/teardown, not beforeAll for per-test state.hardhat_reset or equivalent.hash, fee, value, to, status.typeof, toBeDefined, toBeGreaterThan, toMatch, expect.any(), or missing toHaveBeenCalledWith arguments (R1, R2). toBeUndefined() is acceptable._private properties (R4)..rejects.toThrow() always includes a message or regex (R5).toBe for primitives, toEqual only for deep objects/arrays (R7).DUMMY_/SCREAMING_CASE naming, dummy- (not mock-) for placeholder strings, realistic SDK-shaped fixtures, real BIP-39 seed phrases.tests/ directory, lifecycle hooks used correctly, constants scoped to where they're used, top-level mock refs configured per-test.