---

name: testing
description: Testing and code quality context for Jest, React Testing Library, ESLint, and test patterns. Use when writing tests or fixing linting issues.
last_updated: 2026-04-02
---

# Testing & Quality

**Technologies**: Jest, React Testing Library, Supertest, ESLint

## Required Knowledge
- Jest test framework
- React Testing Library patterns
- API testing with Supertest
- In-memory database testing (MongoDB Memory Server, pg-mem)
- ESLint configuration
- Test-driven development

## Relevant Documentation

| Document | Topics Covered |
|----------|----------------|
| `docs/development/LINTING.md` | ESLint setup, auto-fix, IDE integration |
| `docs/development/BACKEND.md` | Backend testing patterns |
| `docs/development/FRONTEND.md` | Frontend testing patterns |

## Testing Commands

```bash
# Unit tests (in-memory DBs — no services needed, default)
cd backend && npm test
cd backend && npm run test:coverage
./dev.sh test                        # same, runs inside Docker container

# Integration tests (real Docker Compose services)
./dev.sh up                          # start mongo + postgres first
./dev.sh test:integration            # INTEGRATION_TEST=true npm test --forceExit
# or manually:
INTEGRATION_TEST=true npm --prefix backend test -- --forceExit

# Frontend tests
cd frontend && npm test
cd frontend && npm run test:coverage

# Linting
npm run lint
npm run lint:fix
```

## INTEGRATION_TEST env var

`backend/__tests__/setup.js` switches between two modes:

- **`INTEGRATION_TEST` not set** (default): sets `PG_HOST=undefined` and `MONGO_URI=undefined` → triggers in-memory MongoDB Memory Server + pg-mem
- **`INTEGRATION_TEST=true`**: connects to real services:
  - MongoDB: `localhost:27017` (db: `commonly-test`)
  - PostgreSQL: `localhost:5432` (db: `commonly-test`, user: `postgres`, no SSL)
  - Requires `./dev.sh up` to be running first

Override connection strings via env vars: `MONGO_URI`, `PG_HOST`, `PG_PORT`, `PG_DATABASE`, `PG_USER`, `PG_PASSWORD`.

## High-value Current Test Areas

### E2E Integration Tests
- **Two-Way Integration E2E**: `backend/__tests__/service/two-way-integration-e2e.test.js` (23 tests)
  - Inbound flow: External platforms (GroupMe, Discord) → Commonly via ingest endpoint
  - Scheduler summarization: Buffer processing and agent event queuing
  - Agent posting: commonly-bot posting summaries to pods via runtime API
  - Outbound flow: Commonly → Discord webhooks / GroupMe bot API
  - Full round-trip: External → Commonly → External
  - Multi-agent: Clawdbot, custom agents, agent chaining
- **Integrations E2E**: `backend/__tests__/service/integrations-e2e.test.js`
- **Clawdbot E2E**: `backend/__tests__/service/clawdbot-e2e.test.js`

### Contract and Unit Tests
- Integration provider contract tests: `backend/__tests__/contracts/integrationProvider.contract.test.js`.
- Integration catalog + manifest validation routes.
- `backend/__tests__/unit/routes/integrations.catalog.test.js`.
- `backend/__tests__/unit/routes/integrations.validation.test.js`.
- Pod context and skill synthesis.
- `backend/__tests__/unit/routes/pods.context.test.js`.
- `backend/__tests__/unit/services/podContextService.test.js`.
- `backend/__tests__/unit/services/podSkillService.test.js`.

## Backend Testing Patterns

### Unit Test
```javascript
describe('MessageService', () => {
  it('should create a message', async () => {
    const message = await MessageService.create({
      podId: 'pod123',
      userId: 'user123',
      content: 'Hello'
    });
    expect(message.content).toBe('Hello');
  });
});
```

### API Test
```javascript
describe('POST /api/messages', () => {
  it('should create message', async () => {
    const res = await request(app)
      .post('/api/messages')
      .set('Authorization', `Bearer ${token}`)
      .send({ podId: 'pod123', content: 'Hello' });
    expect(res.status).toBe(201);
  });
});
```

## Frontend Testing Patterns

### Component Test
```javascript
describe('ChatRoom', () => {
  it('renders messages', async () => {
    render(<ChatRoom podId="123" />);
    await waitFor(() => {
      expect(screen.getByText('Hello')).toBeInTheDocument();
    });
  });
});
```

### Mock Axios
```javascript
jest.mock('axios');
axios.get.mockResolvedValue({ data: mockMessages });
```

## E2E Integration Testing Patterns

### Two-Way Integration Test Setup
```javascript
// Mock summarizer to avoid Gemini API calls
jest.mock('../../services/summarizerService', () => ({
  generateSummary: jest.fn().mockResolvedValue('AI-generated summary.'),
  summarizePosts: jest.fn().mockResolvedValue({ title: 'Posts Summary', content: 'Summary' }),
  summarizeChats: jest.fn().mockResolvedValue({ title: 'Chats Summary', content: 'Summary' }),
}));

// Mock external APIs
jest.mock('axios');
global.fetch = jest.fn();
```

### Ingest Token Creation Pattern
```javascript
const { hash, randomSecret } = require('../../utils/secret');

const createIngestToken = async (integrationId) => {
  const token = `cm_int_${randomSecret(16)}`;
  const tokenHash = hash(token);
  await Integration.findByIdAndUpdate(integrationId, {
    $push: {
      ingestTokens: {
        tokenHash,
        label: 'Test Token',
        createdBy: testUser._id,
        createdAt: new Date(),
      },
    },
  });
  return token;
};
```

### Agent Runtime Token Pattern
```javascript
// Install agent
await request(app)
  .post('/api/registry/install')
  .set('Authorization', `Bearer ${authToken}`)
  .send({
    agentName: 'commonly-bot',
    podId: testPod._id.toString(),
    scopes: ['context:read', 'summaries:read', 'messages:write'],
  });

// Get runtime token
const tokenRes = await request(app)
  .post(`/api/registry/pods/${testPod._id}/agents/commonly-bot/runtime-tokens`)
  .set('Authorization', `Bearer ${authToken}`)
  .send({ label: 'Test Token' });

const runtimeToken = tokenRes.body.token;

// Post message as agent
await request(app)
  .post(`/api/agents/runtime/pods/${testPod._id}/messages`)
  .set('Authorization', `Bearer ${runtimeToken}`)
  .send({ content: 'Summary content', messageType: 'text' });
```

### Multi-Agent Test Pattern
```javascript
// Install multiple agents on same pod
await Promise.all([
  request(app).post('/api/registry/install').set('Authorization', `Bearer ${authToken}`)
    .send({ agentName: 'commonly-bot', podId: testPod._id.toString(), scopes: [...] }),
  request(app).post('/api/registry/install').set('Authorization', `Bearer ${authToken}`)
    .send({ agentName: 'clawdbot', podId: testPod._id.toString(), scopes: [...] }),
]);

// Enqueue events for each agent
await AgentEventService.enqueue({ agentName: 'commonly-bot', podId, type: 'discord.summary', payload: {...} });
await AgentEventService.enqueue({ agentName: 'clawdbot', podId, type: 'integration.summary', payload: {...} });

// Each agent polls their own events
const commonlyPoll = await request(app).get('/api/agents/runtime/events')
  .set('Authorization', `Bearer ${commonlyToken}`);
const clawdbotPoll = await request(app).get('/api/agents/runtime/events')
  .set('Authorization', `Bearer ${clawdbotToken}`);
```

## ESLint Configuration

```javascript
// .eslintrc.js
module.exports = {
  extends: ['eslint:recommended', 'plugin:react/recommended'],
  rules: {
    'no-unused-vars': 'error',
    'no-console': 'warn'
  }
};
```

## Current Repo Notes (2026-02-08)

Skill catalog is generated into `backend/docs/skills/awesome-agent-skills-index.json`.
Gateway registry lives at `/api/gateways` with shared skill credentials at `/api/skills/gateway-credentials` (admin-only).
Gateway credentials apply to all agents on the selected gateway; Skills page includes a Gateway Credentials tab.
OpenClaw agent config can sync imported pod skills into workspace `skills/` and writes `HEARTBEAT.md` per agent workspace.
Social/activity route coverage now includes:
- `backend/__tests__/unit/routes/users.social.test.js` (user follow/unfollow routes)
- `backend/__tests__/unit/routes/activity.read.test.js` (unread count + mark-read routes)
- `backend/__tests__/unit/routes/posts.test.js` extended for thread follow routes.
