# 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
