MACP

Testing

Running Tests

npm test               # Run all tests once
npm run test:watch     # Watch mode — re-runs on file changes
npm run test:coverage  # Run with v8 coverage report

Test Structure

tests/
├── unit/
│   ├── projections/
│   │   ├── decision.test.ts    # DecisionProjection state machine
│   │   ├── proposal.test.ts    # ProposalProjection state machine
│   │   ├── task.test.ts        # TaskProjection state machine
│   │   ├── handoff.test.ts     # HandoffProjection state machine
│   │   └── quorum.test.ts      # QuorumProjection state machine
│   ├── envelope.test.ts        # Envelope builder functions
│   ├── proto-registry.test.ts  # Protobuf encode/decode roundtrips
│   ├── auth.test.ts            # Auth factory and metadata
│   └── errors.test.ts          # Error class hierarchy
└── integration/
    └── (future) client.test.ts # Requires running runtime

Writing Projection Tests

Projections are pure state machines — they accept envelopes and update internal state. This makes them ideal for unit testing without any I/O:

import { describe, it, expect, beforeEach } from 'vitest';
import { DecisionProjection } from '../../../src/projections/decision';
import { ProtoRegistry } from '../../../src/proto-registry';
import { buildEnvelope } from '../../../src/envelope';
import { MODE_DECISION } from '../../../src/constants';

// Create a real ProtoRegistry — tests the full protobuf round-trip
const registry = new ProtoRegistry();

// Helper to build envelopes with encoded payloads
function makeEnvelope(
  messageType: string,
  payload: Record<string, unknown>,
  sender = 'agent-a',
) {
  return buildEnvelope({
    mode: MODE_DECISION,
    messageType,
    sessionId: 'test-session',
    sender,
    payload: registry.encodeKnownPayload(MODE_DECISION, messageType, payload),
  });
}

describe('DecisionProjection', () => {
  let projection: DecisionProjection;

  beforeEach(() => {
    projection = new DecisionProjection();
  });

  it('tracks proposals and transitions phase', () => {
    projection.applyEnvelope(
      makeEnvelope('Proposal', { proposalId: 'p1', option: 'A' }),
      registry,
    );
    expect(projection.proposals.size).toBe(1);
    expect(projection.phase).toBe('Evaluation');
  });

  it('computes vote totals correctly', () => {
    projection.applyEnvelope(
      makeEnvelope('Proposal', { proposalId: 'p1', option: 'A' }),
      registry,
    );
    projection.applyEnvelope(
      makeEnvelope('Vote', { proposalId: 'p1', vote: 'approve' }, 'alice'),
      registry,
    );
    projection.applyEnvelope(
      makeEnvelope('Vote', { proposalId: 'p1', vote: 'approve' }, 'bob'),
      registry,
    );
    expect(projection.voteTotals()).toEqual({ p1: 2 });
  });
});

Key Testing Pattern

Tests use a real ProtoRegistry instance that loads actual .proto files. This means:

  • Payloads are encoded to protobuf wire format then decoded back
  • Field name casing (camelCase ↔ snake_case) is exercised
  • Missing or extra fields are caught
  • Protobuf default values are handled correctly

What to Test

For each projection:

  • State transitions: Verify phase changes at the right time
  • Record population: Check that maps/arrays are updated correctly
  • Query helpers: Test convenience methods with edge cases
  • Commitment handling: Verify terminal state
  • Mode isolation: Envelopes for other modes are ignored

Proto Registry Tests

Test encode/decode roundtrips for every message type across all modes:

it('decision/Proposal roundtrip', () => {
  const payload = { proposalId: 'p1', option: 'deploy', rationale: 'ready' };
  const encoded = registry.encodeKnownPayload(MODE_DECISION, 'Proposal', payload);
  const decoded = registry.decodeKnownPayload(MODE_DECISION, 'Proposal', encoded);

  expect(decoded).toHaveProperty('proposalId', 'p1');
  expect(decoded).toHaveProperty('option', 'deploy');
});

Integration Tests

Integration tests require a running MACP runtime. They are currently placeholder-only.

To run integration tests against a local runtime:

# Start the runtime
cd /path/to/runtime
MACP_ALLOW_INSECURE=1 MACP_ALLOW_DEV_SENDER_HEADER=1 cargo run

# In another terminal, run integration tests
MACP_RUNTIME_ADDRESS=127.0.0.1:50051 npm test -- tests/integration/

Writing Integration Tests

import { describe, it, expect } from 'vitest';
import { Auth, MacpClient, DecisionSession } from '../../src';

const address = process.env.MACP_RUNTIME_ADDRESS;

describe.skipIf(!address)('MacpClient integration', () => {
  it('initializes successfully', async () => {
    const client = new MacpClient({
      address: address!,
      secure: false,
      auth: Auth.devAgent('test-agent'),
    });
    const init = await client.initialize();
    expect(init.selectedProtocolVersion).toBe('1.0');
    client.close();
  });
});