Testing

This chapter covers testing strategies for applications that use JACS, including unit testing, integration testing, and mocking approaches.

Testing Fundamentals

Test Agent Setup

Create dedicated test configurations to isolate tests from production:

// jacs.test.config.json
{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./test_data",
  "jacs_key_directory": "./test_keys",
  "jacs_agent_private_key_filename": "test_private.pem",
  "jacs_agent_public_key_filename": "test_public.pem",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_default_storage": "fs"
}

Test Fixtures

Set up test fixtures before running tests:

Python (pytest):

import pytest
import jacs
import tempfile
import shutil

@pytest.fixture
def test_agent():
    """Create a test agent with temporary directories."""
    temp_dir = tempfile.mkdtemp()
    data_dir = f"{temp_dir}/data"
    key_dir = f"{temp_dir}/keys"

    # Initialize directories
    import os
    os.makedirs(data_dir)
    os.makedirs(key_dir)

    # Create test config
    config = {
        "jacs_data_directory": data_dir,
        "jacs_key_directory": key_dir,
        "jacs_agent_key_algorithm": "ring-Ed25519",
        "jacs_default_storage": "fs"
    }

    config_path = f"{temp_dir}/jacs.config.json"
    with open(config_path, 'w') as f:
        import json
        json.dump(config, f)

    agent = jacs.JacsAgent()
    agent.load(config_path)

    yield agent

    # Cleanup
    shutil.rmtree(temp_dir)

def test_create_document(test_agent):
    """Test document creation."""
    import json
    doc = test_agent.create_document(json.dumps({
        'title': 'Test Document'
    }))

    assert doc is not None
    parsed = json.loads(doc)
    assert 'jacsId' in parsed
    assert 'jacsSignature' in parsed

Node.js (Jest):

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';
import path from 'path';
import os from 'os';

describe('JACS Document Tests', () => {
  let agent;
  let tempDir;

  beforeAll(() => {
    // Create temp directory
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jacs-test-'));
    const dataDir = path.join(tempDir, 'data');
    const keyDir = path.join(tempDir, 'keys');

    fs.mkdirSync(dataDir);
    fs.mkdirSync(keyDir);

    // Create test config
    const config = {
      jacs_data_directory: dataDir,
      jacs_key_directory: keyDir,
      jacs_agent_key_algorithm: 'ring-Ed25519',
      jacs_default_storage: 'fs'
    };

    const configPath = path.join(tempDir, 'jacs.config.json');
    fs.writeFileSync(configPath, JSON.stringify(config));

    agent = new JacsAgent();
    agent.load(configPath);
  });

  afterAll(() => {
    // Cleanup
    fs.rmSync(tempDir, { recursive: true });
  });

  test('creates a signed document', () => {
    const doc = agent.createDocument(JSON.stringify({
      title: 'Test Document'
    }));

    const parsed = JSON.parse(doc);
    expect(parsed.jacsId).toBeDefined();
    expect(parsed.jacsSignature).toBeDefined();
  });
});

Unit Testing

Testing Document Operations

import pytest
import jacs
import json

def test_document_verification(test_agent):
    """Test that created documents verify correctly."""
    doc = test_agent.create_document(json.dumps({
        'content': 'Test content'
    }))

    is_valid = test_agent.verify_document(doc)
    assert is_valid is True

def test_document_tampering_detected(test_agent):
    """Test that tampered documents fail verification."""
    doc = test_agent.create_document(json.dumps({
        'content': 'Original content'
    }))

    # Tamper with the document
    parsed = json.loads(doc)
    parsed['content'] = 'Tampered content'
    tampered = json.dumps(parsed)

    is_valid = test_agent.verify_document(tampered)
    assert is_valid is False

def test_signature_verification(test_agent):
    """Test signature verification."""
    doc = test_agent.create_document(json.dumps({
        'data': 'test'
    }))

    is_valid = test_agent.verify_signature(doc)
    assert is_valid is True

Testing Agreements

def test_agreement_creation(test_agent):
    """Test creating a document with agreement."""
    doc = test_agent.create_document(json.dumps({
        'contract': 'Service Agreement'
    }))

    # Add agreement
    doc_with_agreement = test_agent.create_agreement(
        doc,
        ['agent-1-id', 'agent-2-id'],
        question='Do you agree to these terms?',
        context='Test agreement'
    )

    parsed = json.loads(doc_with_agreement)
    assert 'jacsAgreement' in parsed
    assert len(parsed['jacsAgreement']['agentIDs']) == 2

def test_agreement_signing(test_agent):
    """Test signing an agreement."""
    doc = test_agent.create_document(json.dumps({
        'contract': 'Test'
    }))

    agent_json = test_agent.load('./jacs.test.config.json')
    agent_data = json.loads(agent_json)
    agent_id = agent_data['jacsId']

    doc_with_agreement = test_agent.create_agreement(
        doc,
        [agent_id],
        question='Agree?'
    )

    signed = test_agent.sign_agreement(doc_with_agreement)

    status_json = test_agent.check_agreement(signed)
    status = json.loads(status_json)

    assert status['complete'] is True

Testing Request/Response Signing

def test_request_signing(test_agent):
    """Test signing a request payload."""
    payload = {
        'method': 'tools/call',
        'params': {'name': 'test_tool'}
    }

    signed = test_agent.sign_request(payload)
    assert signed is not None

    # Verify the signed request
    result = test_agent.verify_response(signed)
    assert result is not None
    assert 'payload' in result

Integration Testing

Testing MCP Integration

Python:

import pytest
import asyncio
from jacs.mcp import JACSMCPServer, JACSMCPClient
from fastmcp import FastMCP

@pytest.fixture
def mcp_server(test_agent):
    """Create a test MCP server."""
    mcp = FastMCP("Test Server")

    @mcp.tool()
    def echo(text: str) -> str:
        return f"Echo: {text}"

    return JACSMCPServer(mcp)

@pytest.mark.asyncio
async def test_mcp_tool_call(mcp_server, test_agent):
    """Test calling an MCP tool with JACS authentication."""
    # This would require setting up actual server/client connection
    # For unit testing, test the signing/verification separately
    pass

Node.js:

import { JACSExpressMiddleware } from 'jacsnpm/http';
import express from 'express';
import request from 'supertest';

describe('JACS Express Middleware', () => {
  let app;
  let agent;

  beforeAll(() => {
    app = express();
    app.use('/api', express.text({ type: '*/*' }));
    app.use('/api', JACSExpressMiddleware({
      configPath: './jacs.test.config.json'
    }));

    app.post('/api/echo', (req, res) => {
      res.send({ echo: req.jacsPayload });
    });

    agent = new JacsAgent();
    agent.load('./jacs.test.config.json');
  });

  test('accepts valid JACS requests', async () => {
    const signedRequest = agent.signRequest({
      message: 'Hello'
    });

    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send(signedRequest);

    expect(response.status).toBe(200);
  });

  test('rejects invalid requests', async () => {
    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send('{"invalid": "request"}');

    expect(response.status).toBe(400);
  });
});

Testing HTTP Endpoints

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
import jacs
import json

app = FastAPI()

@app.post("/api/document")
async def create_doc(request_body: str):
    agent = jacs.JacsAgent()
    agent.load('./jacs.test.config.json')

    result = agent.verify_response(request_body)
    if result:
        # Process the verified payload
        return {"status": "success", "payload": result.get("payload")}
    return {"status": "error"}

@pytest.fixture
def client():
    return TestClient(app)

def test_endpoint_accepts_signed_request(client, test_agent):
    """Test that endpoint accepts properly signed requests."""
    signed = test_agent.sign_request({
        'action': 'create',
        'data': {'title': 'Test'}
    })

    response = client.post("/api/document", content=signed)
    assert response.status_code == 200

Mocking

Mocking JACS Agent

Python:

from unittest.mock import Mock, patch
import json

def test_with_mocked_agent():
    """Test with a mocked JACS agent."""
    mock_agent = Mock()

    # Mock create_document to return a fake signed document
    mock_agent.create_document.return_value = json.dumps({
        'jacsId': 'mock-id',
        'jacsVersion': 'mock-version',
        'content': 'test',
        'jacsSignature': {'signature': 'mock-sig'}
    })

    # Mock verify_document to always return True
    mock_agent.verify_document.return_value = True

    # Use the mock in your tests
    doc = mock_agent.create_document(json.dumps({'content': 'test'}))
    assert mock_agent.verify_document(doc) is True

Node.js:

// Mock for testing
const mockAgent = {
  createDocument: jest.fn().mockReturnValue(JSON.stringify({
    jacsId: 'mock-id',
    jacsVersion: 'mock-version',
    content: 'test',
    jacsSignature: { signature: 'mock-sig' }
  })),
  verifyDocument: jest.fn().mockReturnValue(true),
  signRequest: jest.fn().mockImplementation((payload) =>
    JSON.stringify({ payload, jacsSignature: { signature: 'mock' } })
  ),
  verifyResponse: jest.fn().mockImplementation((response) =>
    ({ payload: JSON.parse(response).payload })
  )
};

test('uses mocked agent', () => {
  const doc = mockAgent.createDocument(JSON.stringify({ test: true }));
  expect(mockAgent.createDocument).toHaveBeenCalled();
  expect(mockAgent.verifyDocument(doc)).toBe(true);
});

Mocking MCP Transport

// Mock transport for MCP testing
class MockTransport {
  constructor() {
    this.messages = [];
  }

  send(message) {
    this.messages.push(message);
  }

  async receive() {
    return this.messages.shift();
  }
}

test('MCP client with mock transport', async () => {
  const mockTransport = new MockTransport();
  // Use mock transport in tests
});

Test Coverage

Python Coverage

# Run tests with coverage
pytest --cov=myapp --cov-report=html tests/

# View coverage report
open htmlcov/index.html

Node.js Coverage

# Run tests with coverage
npm test -- --coverage

# Or with Jest directly
jest --coverage

CI/CD Integration

GitHub Actions

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Generate test keys
        run: |
          mkdir -p test_keys test_data
          # Generate test keys (implementation depends on your setup)

      - name: Run tests
        run: npm test

      - name: Upload coverage
        uses: codecov/codecov-action@v3

Test Environment Variables

# Set test environment
export JACS_TEST_MODE=1
export JACS_TEST_CONFIG=./jacs.test.config.json

Best Practices

1. Isolate Tests

  • Use separate test configurations
  • Create temporary directories for each test run
  • Clean up after tests

2. Test Edge Cases

def test_empty_document():
    """Test handling of empty documents."""
    with pytest.raises(Exception):
        test_agent.create_document('')

def test_invalid_json():
    """Test handling of invalid JSON."""
    with pytest.raises(Exception):
        test_agent.create_document('not json')

def test_large_document():
    """Test handling of large documents."""
    large_content = 'x' * 1000000
    doc = test_agent.create_document(json.dumps({
        'content': large_content
    }))
    assert doc is not None

3. Test Security Properties

def test_signature_changes_with_content():
    """Verify different content produces different signatures."""
    doc1 = test_agent.create_document(json.dumps({'a': 1}))
    doc2 = test_agent.create_document(json.dumps({'a': 2}))

    sig1 = json.loads(doc1)['jacsSignature']['signature']
    sig2 = json.loads(doc2)['jacsSignature']['signature']

    assert sig1 != sig2

4. Test Error Handling

def test_verify_invalid_signature():
    """Test that invalid signatures are rejected."""
    doc = test_agent.create_document(json.dumps({'data': 'test'}))
    parsed = json.loads(doc)

    # Corrupt the signature
    parsed['jacsSignature']['signature'] = 'invalid'
    corrupted = json.dumps(parsed)

    assert test_agent.verify_document(corrupted) is False

See Also