> For the complete documentation index, see [llms.txt](https://amistan.gitbook.io/mep-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://amistan.gitbook.io/mep-docs/testing/testing-guide.md).

# Testing Guide

Comprehensive testing guide for the Morocco Experience Protocol MCP Server.

## Overview

This guide covers all testing aspects of the MEP MCP Server, including unit tests, integration tests, load tests, security tests, and performance tests.

**Current Test Coverage**: \~80%\
**Target Coverage**: 90%+

***

## Table of Contents

1. [Running Tests](#running-tests)
2. [Unit Testing](#unit-testing)
3. [Integration Testing](#integration-testing)
4. [Load Testing](#load-testing)
5. [Security Testing](#security-testing)
6. [Performance Testing](#performance-testing)
7. [Test Data](#test-data)
8. [CI/CD Integration](#cicd-integration)

***

## Running Tests

### Quick Start

```bash
# Run all tests
npm test

# Run with coverage
npm run test:coverage

# Run in watch mode
npm run test:watch

# Run specific test file
npm test -- src/tools/search-experiences-tool.test.ts

# Run tests matching pattern
npm test -- --testNamePattern="should search experiences"
```

### Test Scripts

```bash
# Unit tests
npm test

# Integration tests (requires test database)
npm run test:integration

# Load tests (requires k6)
npm run test:load

# Security audit
npm run security-audit

# Performance profile
npm run performance-profile

# All tests
npm run test:all
```

***

## Unit Testing

### Overview

Unit tests verify individual components in isolation using mocked dependencies.

**Framework**: Jest\
**Coverage Target**: 80%+\
**Location**: `src/**/*.test.ts`

### Test Structure

```typescript
describe('ComponentName', () => {
  describe('methodName', () => {
    it('should do something', () => {
      // Arrange
      const input = { ... };
      
      // Act
      const result = component.method(input);
      
      // Assert
      expect(result).toBe(expected);
    });
  });
});
```

### Running Unit Tests

```bash
# All unit tests
npm test

# Specific component
npm test -- src/tools/search-experiences-tool.test.ts

# With coverage
npm run test:coverage

# Watch mode
npm run test:watch
```

### Writing Unit Tests

#### Example: Testing a Tool

```typescript
// src/tools/search-experiences-tool.test.ts
import { SearchExperiencesTool } from './search-experiences-tool';
import { MockDataAccessLayer } from '../database/data-access-layer';

describe('SearchExperiencesTool', () => {
  let tool: SearchExperiencesTool;
  let mockDataAccess: MockDataAccessLayer;

  beforeEach(() => {
    mockDataAccess = new MockDataAccessLayer();
    tool = new SearchExperiencesTool(mockDataAccess);
  });

  describe('execute', () => {
    it('should return experiences matching query', async () => {
      // Arrange
      const input = {
        query: 'cooking class',
        location: 'Marrakech'
      };

      // Act
      const result = await tool.execute(input);

      // Assert
      expect(result.results).toBeDefined();
      expect(result.results.length).toBeGreaterThan(0);
      expect(result.results[0].title).toContain('Cooking');
    });

    it('should return empty array for no matches', async () => {
      // Arrange
      const input = {
        query: 'nonexistent activity'
      };

      // Act
      const result = await tool.execute(input);

      // Assert
      expect(result.results).toEqual([]);
    });

    it('should filter by location', async () => {
      // Arrange
      const input = {
        query: 'tour',
        location: 'Fes'
      };

      // Act
      const result = await tool.execute(input);

      // Assert
      result.results.forEach(exp => {
        expect(exp.location).toBe('Fes');
      });
    });

    it('should handle validation errors', async () => {
      // Arrange
      const input = {
        query: '' // Invalid: empty query
      };

      // Act & Assert
      await expect(tool.execute(input)).rejects.toThrow('Validation error');
    });
  });
});
```

### Test Coverage

View coverage report:

```bash
npm run test:coverage
open coverage/lcov-report/index.html
```

Coverage targets by component:

* Tools: 90%+
* Data Access Layer: 85%+
* Validation: 95%+
* Security: 90%+
* Protocol Handler: 85%+

***

## Integration Testing

### Overview

Integration tests verify components working together with real dependencies.

**Status**: ⚠️ Needs Implementation\
**Priority**: HIGH\
**Estimated Time**: 2-3 days

### What to Test

1. **End-to-End MCP Flow**
   * Initialize connection
   * List tools
   * Call tools
   * Read resources
   * Receive notifications
2. **Database Integration**
   * Real database queries
   * Data validation
   * Error handling
3. **Cache Integration**
   * Cache hit/miss scenarios
   * Cache invalidation
   * TTL expiration
4. **Multilingual Content**
   * Language parameter handling
   * Fallback logic
   * Content validation

### Setting Up Integration Tests

#### 1. Create Test Database

```bash
# Firestore
firebase projects:create mep-test
firebase use mep-test

# DynamoDB
aws dynamodb create-table \
  --table-name mep-test-experiences \
  --attribute-definitions AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
```

#### 2. Seed Test Data

```bash
npm run staging:seed -- --database=test
```

#### 3. Configure Test Environment

```bash
# .env.test
DATABASE_TYPE=firestore
DATABASE_PROJECT_ID=mep-test
DATABASE_CREDENTIALS=./test-credentials.json
CACHE_ENABLED=true
CACHE_TYPE=memory
```

### Writing Integration Tests

#### Example: E2E MCP Flow

```typescript
// tests/integration/mcp-flow.test.ts
import { MCPServer } from '../../src/mcp-server';
import { getTestDatabase } from '../helpers/test-database';

describe('E2E MCP Flow', () => {
  let server: MCPServer;
  let database: any;

  beforeAll(async () => {
    database = await getTestDatabase();
    server = new MCPServer({ database });
    await server.start();
  });

  afterAll(async () => {
    await server.stop();
    await database.disconnect();
  });

  it('should complete full MCP interaction', async () => {
    // 1. Initialize
    const initResponse = await server.initialize({
      protocolVersion: '2025-06-18',
      capabilities: {},
      clientInfo: { name: 'test-client', version: '1.0.0' }
    });

    expect(initResponse.protocolVersion).toBe('2025-06-18');
    expect(initResponse.capabilities).toBeDefined();

    // 2. List tools
    const toolsResponse = await server.listTools();
    expect(toolsResponse.tools).toHaveLength(4);
    expect(toolsResponse.tools[0].name).toBe('searchExperiences');

    // 3. Call searchExperiences
    const searchResponse = await server.callTool('searchExperiences', {
      query: 'cooking class',
      location: 'Marrakech'
    });

    expect(searchResponse.results).toBeDefined();
    expect(searchResponse.results.length).toBeGreaterThan(0);

    // 4. Get experience details
    const experienceId = searchResponse.results[0].id;
    const detailsResponse = await server.callTool('getExperienceDetails', {
      id: experienceId
    });

    expect(detailsResponse.id).toBe(experienceId);
    expect(detailsResponse.title).toBeDefined();
    expect(detailsResponse.description).toBeDefined();

    // 5. List resources
    const resourcesResponse = await server.listResources();
    expect(resourcesResponse.resources).toBeDefined();
    expect(resourcesResponse.resources.length).toBeGreaterThan(0);

    // 6. Read resource
    const resourceUri = 'mep://knowledge/destinations/marrakech';
    const resourceResponse = await server.readResource(resourceUri);
    expect(resourceResponse.content).toBeDefined();
    expect(resourceResponse.mediaType).toBe('text/markdown');
  });

  it('should handle database errors gracefully', async () => {
    // Simulate database failure
    await database.disconnect();

    await expect(
      server.callTool('searchExperiences', { query: 'test' })
    ).rejects.toThrow('Database unavailable');

    // Reconnect
    await database.connect();
  });

  it('should cache responses correctly', async () => {
    const experienceId = 'exp-001';

    // First call - cache miss
    const start1 = Date.now();
    const result1 = await server.callTool('getExperienceDetails', {
      id: experienceId
    });
    const duration1 = Date.now() - start1;

    // Second call - cache hit
    const start2 = Date.now();
    const result2 = await server.callTool('getExperienceDetails', {
      id: experienceId
    });
    const duration2 = Date.now() - start2;

    // Cache hit should be faster
    expect(duration2).toBeLessThan(duration1);
    expect(result1).toEqual(result2);
  });
});
```

### Running Integration Tests

```bash
# Set up test environment
export NODE_ENV=test
export DATABASE_TYPE=firestore
export DATABASE_PROJECT_ID=mep-test

# Run integration tests
npm run test:integration

# Run specific integration test
npm run test:integration -- tests/integration/mcp-flow.test.ts
```

***

## Load Testing

### Overview

Load tests verify the system can handle expected traffic volumes.

**Tool**: k6\
**Status**: ⚠️ Needs Implementation\
**Priority**: MEDIUM

### Performance Targets

* **Concurrent Connections**: 1000+
* **Requests per Second**: 500 sustained
* **Response Time p95**: < 200ms
* **Error Rate**: < 0.1%

### Installing k6

```bash
# macOS
brew install k6

# Linux
sudo apt-get install k6

# Windows
choco install k6
```

### Load Test Scenarios

#### 1. Sustained Load Test

```javascript
// tests/load/sustained-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
  stages: [
    { duration: '2m', target: 100 },   // Ramp up to 100 users
    { duration: '10m', target: 500 },  // Stay at 500 users
    { duration: '2m', target: 0 },     // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<200'],  // 95% under 200ms
    http_req_failed: ['rate<0.001'],   // Error rate < 0.1%
  },
};

export default function () {
  const payload = JSON.stringify({
    jsonrpc: '2.0',
    method: 'tools/call',
    params: {
      name: 'searchExperiences',
      arguments: {
        query: 'cooking',
        location: 'Marrakech'
      }
    },
    id: 1
  });

  const res = http.post('https://api.cometomorocco.com/sse', payload, {
    headers: { 'Content-Type': 'application/json' },
  });

  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
    'has results': (r) => JSON.parse(r.body).result.results.length > 0,
  });

  sleep(1);
}
```

#### 2. Spike Test

```javascript
// tests/load/spike-test.js
export let options = {
  stages: [
    { duration: '30s', target: 0 },     // Start at 0
    { duration: '30s', target: 2000 },  // Spike to 2000
    { duration: '1m', target: 2000 },   // Stay at 2000
    { duration: '30s', target: 0 },     // Ramp down
  ],
};

// ... same test function as above
```

#### 3. Stress Test

```javascript
// tests/load/stress-test.js
export let options = {
  stages: [
    { duration: '2m', target: 100 },
    { duration: '5m', target: 500 },
    { duration: '5m', target: 1000 },
    { duration: '5m', target: 2000 },
    { duration: '5m', target: 3000 },  // Push beyond limits
    { duration: '2m', target: 0 },
  ],
};

// ... same test function as above
```

### Running Load Tests

```bash
# Sustained load test
k6 run tests/load/sustained-load.js

# Spike test
k6 run tests/load/spike-test.js

# Stress test
k6 run tests/load/stress-test.js

# With custom options
k6 run --vus 100 --duration 30s tests/load/sustained-load.js

# Output to file
k6 run --out json=results.json tests/load/sustained-load.js
```

### Analyzing Results

```bash
# View summary
k6 run tests/load/sustained-load.js

# Generate HTML report
k6 run --out json=results.json tests/load/sustained-load.js
k6-reporter results.json --output report.html
```

***

## Security Testing

### Overview

Security tests verify the system is protected against common vulnerabilities.

**Status**: ✅ Framework Complete\
**Priority**: HIGH

### Running Security Audit

```bash
npm run security-audit
```

### Security Test Categories

1. **Authentication & Authorization**
   * Public API access
   * Verified operators only
   * No sensitive data exposure
2. **Rate Limiting**
   * 100 req/min enforcement
   * HTTP 429 responses
   * Retry-After headers
3. **Input Sanitization**
   * SQL injection prevention
   * XSS prevention
   * Path traversal prevention
   * NoSQL injection prevention
4. **OWASP Top 10**
   * A01: Broken Access Control
   * A02: Cryptographic Failures
   * A03: Injection
   * A04: Insecure Design
   * A05: Security Misconfiguration
   * A06: Vulnerable Components
   * A07: Authentication Failures
   * A08: Data Integrity Failures
   * A09: Logging & Monitoring Failures
   * A10: SSRF
5. **Security Headers**
   * HSTS
   * X-Content-Type-Options
   * X-Frame-Options
   * Content-Security-Policy
   * X-XSS-Protection

### Manual Security Testing

#### Test SQL Injection

```bash
curl -X POST https://api.cometomorocco.com/sse \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "searchExperiences",
      "arguments": {
        "query": "'; DROP TABLE experiences; --"
      }
    },
    "id": 1
  }'
```

Expected: Input sanitized, no SQL injection

#### Test XSS

```bash
curl -X POST https://api.cometomorocco.com/sse \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "searchExperiences",
      "arguments": {
        "query": "<script>alert(\"XSS\")</script>"
      }
    },
    "id": 1
  }'
```

Expected: Input sanitized, no XSS

#### Test Rate Limiting

```bash
# Send 101 requests rapidly
for i in {1..101}; do
  curl https://api.cometomorocco.com/health &
done
wait
```

Expected: HTTP 429 after 100 requests

***

## Performance Testing

### Overview

Performance tests verify the system meets response time and resource usage targets.

**Status**: ✅ Framework Complete\
**Priority**: HIGH

### Running Performance Profile

```bash
npm run performance-profile
```

### Performance Targets

* **Bundle Size**: < 500KB
* **Tool Execution**: < 100ms (p95)
* **Database Queries**: < 50ms (p95)
* **Cache Hit Rate**: > 70%
* **Memory Usage**: < 100MB
* **Cold Start**: < 500ms

### Performance Test Categories

1. **Bundle Size Analysis**
   * Main bundle size
   * Dependency count
   * Heavy dependencies check
2. **Database Query Performance**
   * Query execution time
   * Index usage
   * Slow query detection
3. **Caching Performance**
   * Cache hit rate
   * Cache TTL effectiveness
   * Cache invalidation
4. **Response Time Analysis**
   * Tool execution time
   * Database query time
   * Cache hit time
   * Total request time
5. **Memory Usage**
   * Heap usage
   * Memory leaks
   * Resource cleanup

### Manual Performance Testing

#### Test Response Time

```bash
# Using curl with timing
curl -w "@curl-format.txt" -o /dev/null -s \
  -X POST https://api.cometomorocco.com/sse \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "searchExperiences",
      "arguments": { "query": "cooking" }
    },
    "id": 1
  }'

# curl-format.txt:
# time_namelookup:  %{time_namelookup}\n
# time_connect:  %{time_connect}\n
# time_starttransfer:  %{time_starttransfer}\n
# time_total:  %{time_total}\n
```

#### Test Cache Performance

```bash
# First request (cache miss)
time curl -X POST https://api.cometomorocco.com/sse \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"getExperienceDetails","arguments":{"id":"exp-001"}},"id":1}'

# Second request (cache hit)
time curl -X POST https://api.cometomorocco.com/sse \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"getExperienceDetails","arguments":{"id":"exp-001"}},"id":1}'
```

Expected: Second request significantly faster

***

## Test Data

### Sample Data

Located in `src/database/sample-data.ts`:

* 50+ experiences across 5 cities
* 20+ operators (guides, artisans, tour operators)
* Multilingual content (English, French, Arabic)
* Various categories (culinary, adventure, cultural, etc.)

### Staging Data

Located in `staging-data/`:

* `experiences.json`: Production-like experience data
* `operators.json`: Production-like operator data

### Loading Test Data

```bash
# Load sample data (for unit tests)
# Automatically loaded by mock implementations

# Load staging data (for integration tests)
npm run staging:seed

# Validate staging data
npm run staging:validate

# Clear staging data
npm run staging:seed:clear
```

***

## CI/CD Integration

### GitHub Actions

Tests run automatically on:

* Pull requests
* Pushes to `develop` branch
* Pushes to `main` branch

### CI Pipeline

```yaml
# .github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test
      - run: npm run security-audit
      - run: npm run performance-profile
```

### Pre-Commit Hooks

```bash
# Install husky
npm install --save-dev husky

# Set up pre-commit hook
npx husky install
npx husky add .husky/pre-commit "npm test"
```

***

## Best Practices

1. **Write tests first** (TDD): Write failing tests, then implement
2. **Test behavior, not implementation**: Focus on what, not how
3. **Use descriptive test names**: "should return experiences matching query"
4. **Keep tests independent**: Each test should run in isolation
5. **Mock external dependencies**: Use mocks for database, cache, HTTP
6. **Test edge cases**: Empty inputs, null values, boundary conditions
7. **Test error paths**: Ensure errors are handled correctly
8. **Maintain test data**: Keep sample data up to date
9. **Run tests frequently**: Run tests before committing
10. **Monitor test performance**: Keep tests fast (< 1s per test)

***

## Troubleshooting

### Tests Failing

```bash
# Clear Jest cache
npm test -- --clearCache

# Run tests in band (no parallel)
npm test -- --runInBand

# Increase timeout
npm test -- --testTimeout=10000
```

### Coverage Issues

```bash
# Generate detailed coverage report
npm run test:coverage -- --verbose

# View coverage for specific file
npm run test:coverage -- src/tools/search-experiences-tool.ts
```

### Performance Issues

```bash
# Profile test execution
npm test -- --logHeapUsage

# Run specific slow test
npm test -- --testNamePattern="slow test"
```

***

## Next Steps

1. **Complete integration tests** (2-3 days)
2. **Implement load tests** (2-3 days)
3. **Run security audit in production** (1 day)
4. **Optimize based on performance profile** (1-2 days)
5. **Achieve 90%+ test coverage** (1-2 days)

***

## Support

* **Email**: <support@cometomorocco.com>
* **Documentation**: <https://docs.cometomorocco.com>
* **GitHub Issues**: \[Repository URL]/issues

***

**Last Updated**: December 3, 2025\
**Version**: 1.0.0


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://amistan.gitbook.io/mep-docs/testing/testing-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
