macp-sdk-typescriptGuides
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 reportTest 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 runtimeWriting 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
phasechanges 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();
});
});