> ## Documentation Index
> Fetch the complete documentation index at: https://ouroborai.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Test Patterns

> Testing conventions for TypeScript and Rust

ouroborai maintains 419 TypeScript tests (38 files) and 115 Rust tests across
the four Stylus contracts. This page documents the testing conventions and
patterns used throughout the codebase.

## TypeScript (Vitest)

The project uses Vitest v3 with globals enabled. All tests run through the
workspace Vitest configuration.

### Running Tests

```bash theme={"theme":{"light":"dracula","dark":"dracula"}}
# Run all TypeScript tests
./node_modules/.bin/vitest run

# Run a specific test file
./node_modules/.bin/vitest run apps/api/src/routes/agent.test.ts

# Watch mode
./node_modules/.bin/vitest watch
```

<Warning>
  Do NOT use `bun test` or `npx vitest` -- version mismatches between Bun's
  bundled test runner and the project's Vitest installation cause failures.
  Always use `./node_modules/.bin/vitest run`.
</Warning>

### vi.hoisted for Mock Variables

When mock variables need to be referenced inside `vi.mock()` factory
functions, use `vi.hoisted()` to ensure they are initialized before the
mock factory runs:

```typescript theme={"theme":{"light":"dracula","dark":"dracula"}}
const mocks = vi.hoisted(() => ({
  mockFetch: vi.fn(),
  mockSend: vi.fn(),
}));

vi.mock("../utils/fetch.js", () => ({
  safeFetch: mocks.mockFetch,
}));

describe("MyModule", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("calls fetch", async () => {
    mocks.mockFetch.mockResolvedValue({ ok: true });
    // ... test logic
    expect(mocks.mockFetch).toHaveBeenCalledOnce();
  });
});
```

<Info>
  `vi.hoisted()` lifts the callback above all `vi.mock()` calls in the module
  scope. Without it, mock variables would be `undefined` when the factory
  runs.
</Info>

### vi.clearAllMocks vs vi.restoreAllMocks

Use `vi.clearAllMocks()` in `beforeEach` -- not `vi.restoreAllMocks()`:

```typescript theme={"theme":{"light":"dracula","dark":"dracula"}}
beforeEach(() => {
  vi.clearAllMocks();  // correct
  // vi.restoreAllMocks();  // wrong -- strips mockImplementation
});
```

`restoreAllMocks()` removes `mockImplementation` callbacks, which breaks
tests that rely on them. `clearAllMocks()` resets call counts and return
values while preserving the mock structure.

### Mocking with vi.mock

Factory-based mocking for module dependencies:

```typescript theme={"theme":{"light":"dracula","dark":"dracula"}}
vi.mock("@arb-agent/adapters", () => ({
  UniswapV3Adapter: vi.fn().mockImplementation(() => ({
    quote: vi.fn().mockResolvedValue({
      amountOut: 1000000n,
      priceImpactBps: 5,
      route: [],
      fee: 100n,
    }),
    swap: vi.fn().mockResolvedValue({ hash: "0xabc", success: true }),
  })),
}));
```

### Globals Enabled

The Vitest config has `globals: true`, so `describe`, `it`, `expect`,
`beforeEach`, `vi`, etc. are available without imports:

```typescript theme={"theme":{"light":"dracula","dark":"dracula"}}
// No import needed -- globals are enabled
describe("swap router", () => {
  it("returns a quote", async () => {
    const res = await app.request("/swap/quote?tokenIn=ETH&tokenOut=USDC&amountIn=1000");
    expect(res.status).toBe(200);
  });
});
```

### Testing Hono Routes

Use Hono's built-in `app.request()` for route testing:

```typescript theme={"theme":{"light":"dracula","dark":"dracula"}}
import { Hono } from "hono";
import { swapRouter } from "./swap.js";

const app = new Hono();
app.route("/swap", swapRouter);

describe("swap routes", () => {
  it("GET /swap/quote returns 400 without params", async () => {
    const res = await app.request("/swap/quote");
    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.error).toContain("Missing required params");
  });
});
```

***

## Rust (Stylus Contracts)

Stylus contracts use `cargo test` with the `stylus-test` feature flag for
testing utilities.

### Running Tests

```bash theme={"theme":{"light":"dracula","dark":"dracula"}}
# Run all Rust tests
cargo test --features stylus-test \
  --manifest-path contracts/Cargo.toml

# Run tests for a specific contract
cargo test --features stylus-test \
  --manifest-path contracts/agent-registry/Cargo.toml

# Run with output
cargo test --features stylus-test \
  --manifest-path contracts/Cargo.toml -- --nocapture
```

### TestVM Setup Pattern

Every test creates a `TestVM` and instantiates the contract from it:

```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
use stylus_test::TestVM;
use alloy_primitives::{address, Address, U256};

const ALICE: Address = address!("aaa0000000000000000000000000000000000001");
const BOB: Address = address!("bbb0000000000000000000000000000000000002");

fn setup() -> (TestVM, MyContract) {
    let vm = TestVM::new();
    let contract = MyContract::from(&vm);
    (vm, contract)
}

#[test]
fn test_something() {
    let (vm, mut contract) = setup();
    vm.set_sender(ALICE);
    // ... test logic
}
```

### Setting Sender

Use `vm.set_sender()` to simulate calls from different addresses:

```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
vm.set_sender(ALICE);
let id = contract.register(capabilities, 500).unwrap();

vm.set_sender(BOB);
let ok = contract.update(id, new_caps, 0).unwrap();
assert!(!ok); // BOB is not the owner
```

### Mocking External Calls

Use `mock_call` for state-changing calls and `mock_static_call` for view
calls:

```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
// Mock an ERC20 transferFrom (state-changing)
let calldata = transferFromCall { from, to, amount }.abi_encode();
vm.mock_call(USDC_ADDR, calldata, U256::ZERO, Ok(encode_bool_true()));

// Mock a balanceOf (view)
let calldata = balanceOfCall { account }.abi_encode();
vm.mock_static_call(USDC_ADDR, calldata, Ok(balance.to_be_bytes_vec()));
```

<Warning>
  `mock_static_call` in stylus-test 0.10.0 always returns the **LAST**
  registered mock's data when multiple mocks target the same contract.
  Register losing mocks first, winning mock last.
</Warning>

### Required Imports

Stylus tests require specific imports that differ from the main code:

```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
// Test imports -- use from alloy_primitives, not stylus_sdk
use alloy_primitives::{address, Address, B256, U256};
use alloy_sol_types::SolCall;
use stylus_test::TestVM;

// Main code imports
use stylus_sdk::alloy_primitives::{Address, U256};
```

### Verifying Events

Check emitted events via `vm.get_emitted_logs()`:

```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
let logs = vm.get_emitted_logs();
assert!(!logs.is_empty());

let (topics, data) = &logs[logs.len() - 1];
// topics[0] = event signature hash
// topics[1..] = indexed parameters
// data = non-indexed parameters ABI-encoded

let indexed_param = B256::left_padding_from(ALICE.as_slice());
assert_eq!(topics[1], indexed_param);

let amount = U256::from_be_slice(&data[0..32]);
assert_eq!(amount, expected_amount);
```

### Private Helpers

Private helper functions that take `&mut` parameters must live in a separate
`impl` block (not `#[public]`). The Stylus SDK requires ABI-compatible types
in all `#[public]` methods:

```rust theme={"theme":{"light":"dracula","dark":"dracula"}}
// Public methods
#[public]
impl MyContract {
    pub fn do_thing(&mut self) -> Result<(), Vec<u8>> {
        self.helper_method()?;
        Ok(())
    }
}

// Private helpers in a separate impl block
impl MyContract {
    fn helper_method(&mut self) -> Result<(), Vec<u8>> {
        // ... internal logic with &mut params
        Ok(())
    }
}
```
