Skip to content

Integration Testing Guide

Comprehensive Integration Testing for Altus 4

Integration tests verify that different components of Altus 4 work correctly together, including API endpoints, database interactions, and service integrations. This guide covers strategies, patterns, and best practices for effective integration testing.

Integration Testing Philosophy

What to Test

Integration tests focus on:

  • API Endpoints: Complete request/response cycles
  • Database Operations: Real database interactions
  • Service Integration: How services work together
  • External Dependencies: Third-party API integrations
  • Authentication Flow: End-to-end auth processes
  • Error Handling: System behavior under failure conditions

Integration vs Unit Tests

AspectUnit TestsIntegration Tests
ScopeSingle componentMultiple components
SpeedVery fast (<1ms)Moderate (10-100ms)
DependenciesMockedReal or test doubles
EnvironmentIsolatedTest environment
PurposeLogic verificationComponent interaction

Test Environment Setup

Test Database Configuration

typescript
// tests/config/database.ts
import mysql from 'mysql2/promise';

export class TestDatabaseManager {
  private connection: mysql.Connection | null = null;

  async setupTestDatabase(): Promise<void> {
    this.connection = await mysql.createConnection({
      host: process.env.TEST_DB_HOST || 'localhost',
      port: parseInt(process.env.TEST_DB_PORT || '3306'),
      user: process.env.TEST_DB_USER || 'altus4_test',
      password: process.env.TEST_DB_PASSWORD || 'test_password',
      database: process.env.TEST_DB_NAME || 'altus4_test',
      multipleStatements: true,
    });

    // Create test schema
    await this.createTestSchema();

    // Seed initial data
    await this.seedTestData();
  }

  async cleanupTestDatabase(): Promise<void> {
    if (this.connection) {
      await this.clearTestData();
      await this.connection.end();
      this.connection = null;
    }
  }

  private async createTestSchema(): Promise<void> {
    const schema = `
      CREATE TABLE IF NOT EXISTS test_articles (
        id INT AUTO_INCREMENT PRIMARY KEY,
        title VARCHAR(255) NOT NULL,
        content TEXT NOT NULL,
        author VARCHAR(100),
        published_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FULLTEXT KEY ft_title_content (title, content),
        FULLTEXT KEY ft_title (title)
      );

      CREATE TABLE IF NOT EXISTS test_products (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        description TEXT,
        category VARCHAR(100),
        price DECIMAL(10,2),
        FULLTEXT KEY ft_name_description (name, description)
      );
    `;

    await this.connection!.execute(schema);
  }

  private async seedTestData(): Promise<void> {
    const articles = [
      {
        title: 'MySQL Performance Optimization Guide',
        content:
          'Comprehensive guide to optimizing MySQL database performance including indexing strategies, query optimization, and server configuration.',
        author: 'Database Expert',
      },
      {
        title: 'Advanced Database Indexing',
        content:
          'Learn about B-tree indexes, composite indexes, and full-text search optimization techniques for better query performance.',
        author: 'Index Specialist',
      },
      {
        title: 'Query Performance Tuning',
        content:
          'Best practices for writing efficient SQL queries, avoiding common pitfalls, and using EXPLAIN to analyze query execution plans.',
        author: 'SQL Expert',
      },
    ];

    for (const article of articles) {
      await this.connection!.execute(
        'INSERT INTO test_articles (title, content, author) VALUES (?, ?, ?)',
        [article.title, article.content, article.author]
      );
    }

    const products = [
      {
        name: 'Gaming Laptop Pro',
        description:
          'High-performance gaming laptop with RTX graphics and fast SSD storage',
        category: 'Electronics',
        price: 1299.99,
      },
      {
        name: 'Wireless Gaming Mouse',
        description: 'Precision wireless mouse designed for competitive gaming',
        category: 'Accessories',
        price: 79.99,
      },
    ];

    for (const product of products) {
      await this.connection!.execute(
        'INSERT INTO test_products (name, description, category, price) VALUES (?, ?, ?, ?)',
        [product.name, product.description, product.category, product.price]
      );
    }
  }

  private async clearTestData(): Promise<void> {
    await this.connection!.execute('DELETE FROM test_articles');
    await this.connection!.execute('DELETE FROM test_products');
  }

  async getConnection(): Promise<mysql.Connection> {
    if (!this.connection) {
      throw new Error('Test database not initialized');
    }
    return this.connection;
  }
}

// Global test database instance
export const testDb = new TestDatabaseManager();

Test Server Setup

typescript
// tests/helpers/test-server.ts
import { app } from '@/app';
import { Server } from 'http';
import { testDb } from '../config/database';

export class TestServer {
  private server: Server | null = null;
  private port: number;

  constructor(port: number = 0) {
    this.port = port;
  }

  async start(): Promise<void> {
    // Setup test database
    await testDb.setupTestDatabase();

    // Start server
    return new Promise((resolve, reject) => {
      this.server = app.listen(this.port, (err?: Error) => {
        if (err) {
          reject(err);
        } else {
          const address = this.server!.address();
          if (typeof address === 'object' && address) {
            this.port = address.port;
          }
          resolve();
        }
      });
    });
  }

  async stop(): Promise<void> {
    // Cleanup database
    await testDb.cleanupTestDatabase();

    // Stop server
    if (this.server) {
      return new Promise(resolve => {
        this.server!.close(() => {
          this.server = null;
          resolve();
        });
      });
    }
  }

  getPort(): number {
    return this.port;
  }

  getBaseUrl(): string {
    return `http://localhost:${this.port}`;
  }
}

API Integration Testing

Authentication Testing

typescript
// tests/integration/auth/authentication.test.ts
import request from 'supertest';
import { TestServer } from '../../helpers/test-server';
import { createTestUser, createTestApiKey } from '../../helpers/auth-helpers';

describe('Authentication Integration', () => {
  let testServer: TestServer;
  let baseUrl: string;

  beforeAll(async () => {
    testServer = new TestServer();
    await testServer.start();
    baseUrl = testServer.getBaseUrl();
  });

  afterAll(async () => {
    await testServer.stop();
  });

  describe('User Registration', () => {
    it('should register a new user successfully', async () => {
      const userData = {
        name: 'Test User',
        email: 'test@example.com',
        password: 'securePassword123',
      };

      const response = await request(baseUrl)
        .post('/api/v1/auth/register')
        .send(userData)
        .expect(201);

      expect(response.body).toMatchObject({
        success: true,
        data: {
          user: {
            id: expect.any(String),
            name: userData.name,
            email: userData.email,
            role: 'user',
          },
        },
      });

      // Verify user was created in database
      const connection = await testDb.getConnection();
      const [users] = await connection.execute(
        'SELECT * FROM users WHERE email = ?',
        [userData.email]
      );

      expect(users).toHaveLength(1);
      expect((users as any[])[0].email).toBe(userData.email);
    });

    it('should reject duplicate email registration', async () => {
      const userData = {
        name: 'Test User',
        email: 'duplicate@example.com',
        password: 'securePassword123',
      };

      // First registration
      await request(baseUrl)
        .post('/api/v1/auth/register')
        .send(userData)
        .expect(201);

      // Duplicate registration
      const response = await request(baseUrl)
        .post('/api/v1/auth/register')
        .send(userData)
        .expect(400);

      expect(response.body).toMatchObject({
        success: false,
        error: {
          code: 'EMAIL_EXISTS',
          message: expect.stringContaining('email'),
        },
      });
    });

    it('should validate required fields', async () => {
      const invalidData = {
        name: 'Test User',
        // Missing email and password
      };

      const response = await request(baseUrl)
        .post('/api/v1/auth/register')
        .send(invalidData)
        .expect(400);

      expect(response.body.error.code).toBe('VALIDATION_ERROR');
    });
  });

  describe('User Login', () => {
    let testUser: any;

    beforeEach(async () => {
      testUser = await createTestUser({
        email: 'login-test@example.com',
        password: 'testPassword123',
        name: 'Login Test User',
      });
    });

    it('should login with valid credentials', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/auth/login')
        .send({
          email: testUser.email,
          password: 'testPassword123',
        })
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        data: {
          token: expect.any(String),
          user: {
            id: testUser.id,
            email: testUser.email,
            name: testUser.name,
          },
        },
      });

      // Verify JWT token structure
      const token = response.body.data.token;
      expect(token).toMatch(/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/);
    });

    it('should reject invalid credentials', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/auth/login')
        .send({
          email: testUser.email,
          password: 'wrongPassword',
        })
        .expect(401);

      expect(response.body).toMatchObject({
        success: false,
        error: {
          code: 'INVALID_CREDENTIALS',
          message: expect.any(String),
        },
      });
    });
  });

  describe('API Key Management', () => {
    let testUser: any;
    let authToken: string;

    beforeEach(async () => {
      testUser = await createTestUser();

      // Get auth token
      const loginResponse = await request(baseUrl)
        .post('/api/v1/auth/login')
        .send({
          email: testUser.email,
          password: 'testPassword123',
        });

      authToken = loginResponse.body.data.token;
    });

    it('should create first API key via setup endpoint', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/management/setup')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(201);

      expect(response.body).toMatchObject({
        success: true,
        data: {
          apiKey: {
            id: expect.any(String),
            key: expect.stringMatching(/^altus4_sk_test_/),
            name: 'Default API Key',
            environment: 'test',
            permissions: ['search'],
          },
        },
      });
    });

    it('should create additional API keys', async () => {
      // First create initial API key
      const setupResponse = await request(baseUrl)
        .post('/api/v1/management/setup')
        .set('Authorization', `Bearer ${authToken}`);

      const firstApiKey = setupResponse.body.data.apiKey.key;

      // Create additional API key
      const response = await request(baseUrl)
        .post('/api/v1/keys')
        .set('Authorization', `Bearer ${firstApiKey}`)
        .send({
          name: 'Production API Key',
          environment: 'live',
          permissions: ['search', 'analytics'],
        })
        .expect(201);

      expect(response.body.data.apiKey.key).toMatch(/^altus4_sk_live_/);
      expect(response.body.data.apiKey.permissions).toEqual([
        'search',
        'analytics',
      ]);
    });
  });
});

Search API Integration Testing

typescript
// tests/integration/api/search.test.ts
import request from 'supertest';
import { TestServer } from '../../helpers/test-server';
import {
  createTestUser,
  createTestApiKey,
  createTestDatabase,
} from '../../helpers/auth-helpers';

describe('Search API Integration', () => {
  let testServer: TestServer;
  let baseUrl: string;
  let testUser: any;
  let testApiKey: string;
  let testDatabaseId: string;

  beforeAll(async () => {
    testServer = new TestServer();
    await testServer.start();
    baseUrl = testServer.getBaseUrl();

    // Setup test user and API key
    testUser = await createTestUser();
    testApiKey = await createTestApiKey(testUser.id, {
      permissions: ['search', 'analytics'],
      environment: 'test',
    });

    // Create test database connection
    testDatabaseId = await createTestDatabase(testUser.id, {
      name: 'Test Database',
      host: 'localhost',
      database: 'altus4_test',
      tables: ['test_articles', 'test_products'],
    });
  });

  afterAll(async () => {
    await testServer.stop();
  });

  describe('POST /api/v1/search', () => {
    it('should execute basic search successfully', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'mysql performance',
          databases: [testDatabaseId],
          searchMode: 'natural',
          limit: 10,
        })
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        data: {
          results: expect.any(Array),
          totalCount: expect.any(Number),
          executionTime: expect.any(Number),
          categories: expect.any(Array),
          suggestions: expect.any(Array),
        },
        meta: {
          timestamp: expect.any(String),
          requestId: expect.any(String),
          version: expect.any(String),
        },
      });

      // Verify search results structure
      if (response.body.data.results.length > 0) {
        const result = response.body.data.results[0];
        expect(result).toMatchObject({
          id: expect.any(String),
          database: testDatabaseId,
          table: expect.any(String),
          data: expect.any(Object),
          relevanceScore: expect.any(Number),
          snippet: expect.any(String),
          matchedColumns: expect.any(Array),
        });
      }
    });

    it('should handle semantic search mode', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'database optimization techniques',
          databases: [testDatabaseId],
          searchMode: 'semantic',
          limit: 15,
        })
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data.results).toBeInstanceOf(Array);
    });

    it('should handle boolean search mode', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: '+mysql +performance -slow',
          databases: [testDatabaseId],
          searchMode: 'boolean',
          limit: 20,
        })
        .expect(200);

      expect(response.body.success).toBe(true);
    });

    it('should support table filtering', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'performance',
          databases: [testDatabaseId],
          tables: ['test_articles'],
          limit: 10,
        })
        .expect(200);

      expect(response.body.success).toBe(true);

      // All results should be from the specified table
      response.body.data.results.forEach((result: any) => {
        expect(result.table).toBe('test_articles');
      });
    });

    it('should support column filtering', async () => {
      const response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'mysql',
          databases: [testDatabaseId],
          columns: ['title'],
          limit: 10,
        })
        .expect(200);

      expect(response.body.success).toBe(true);
    });

    it('should handle pagination correctly', async () => {
      // First page
      const page1Response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'database',
          databases: [testDatabaseId],
          limit: 2,
          offset: 0,
        })
        .expect(200);

      // Second page
      const page2Response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'database',
          databases: [testDatabaseId],
          limit: 2,
          offset: 2,
        })
        .expect(200);

      expect(page1Response.body.success).toBe(true);
      expect(page2Response.body.success).toBe(true);

      // Results should be different (assuming more than 2 total results)
      if (page1Response.body.data.totalCount > 2) {
        const page1Ids = page1Response.body.data.results.map((r: any) => r.id);
        const page2Ids = page2Response.body.data.results.map((r: any) => r.id);

        expect(page1Ids).not.toEqual(page2Ids);
      }
    });

    it('should validate request parameters', async () => {
      // Empty query
      const emptyQueryResponse = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: '',
          databases: [testDatabaseId],
        })
        .expect(400);

      expect(emptyQueryResponse.body.error.code).toBe('VALIDATION_ERROR');

      // No databases
      const noDatabasesResponse = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'test query',
          databases: [],
        })
        .expect(400);

      expect(noDatabasesResponse.body.error.code).toBe('VALIDATION_ERROR');

      // Invalid limit
      const invalidLimitResponse = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'test query',
          databases: [testDatabaseId],
          limit: 500, // Exceeds maximum
        })
        .expect(400);

      expect(invalidLimitResponse.body.error.code).toBe('VALIDATION_ERROR');
    });

    it('should handle authentication errors', async () => {
      // No API key
      await request(baseUrl)
        .post('/api/v1/search')
        .send({
          query: 'test query',
          databases: [testDatabaseId],
        })
        .expect(401);

      // Invalid API key
      await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', 'Bearer invalid_key')
        .send({
          query: 'test query',
          databases: [testDatabaseId],
        })
        .expect(401);
    });

    it('should handle permission errors', async () => {
      // Create API key without search permission
      const limitedApiKey = await createTestApiKey(testUser.id, {
        permissions: ['analytics'], // No search permission
        environment: 'test',
      });

      const response = await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${limitedApiKey}`)
        .send({
          query: 'test query',
          databases: [testDatabaseId],
        })
        .expect(403);

      expect(response.body.error.code).toBe('INSUFFICIENT_PERMISSIONS');
    });
  });

  describe('GET /api/v1/search/suggestions', () => {
    it('should return search suggestions', async () => {
      const response = await request(baseUrl)
        .get('/api/v1/search/suggestions')
        .set('Authorization', `Bearer ${testApiKey}`)
        .query({
          query: 'mysql perf',
          databases: testDatabaseId,
          limit: 5,
        })
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        data: {
          suggestions: expect.any(Array),
        },
      });

      // Verify suggestion structure
      if (response.body.data.suggestions.length > 0) {
        const suggestion = response.body.data.suggestions[0];
        expect(suggestion).toMatchObject({
          text: expect.any(String),
          score: expect.any(Number),
          type: expect.any(String),
        });
      }
    });
  });

  describe('GET /api/v1/search/history', () => {
    it('should return user search history', async () => {
      // Perform a search first to create history
      await request(baseUrl)
        .post('/api/v1/search')
        .set('Authorization', `Bearer ${testApiKey}`)
        .send({
          query: 'mysql optimization',
          databases: [testDatabaseId],
        });

      // Get search history
      const response = await request(baseUrl)
        .get('/api/v1/search/history')
        .set('Authorization', `Bearer ${testApiKey}`)
        .query({ limit: 10 })
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        data: {
          searches: expect.any(Array),
          totalCount: expect.any(Number),
        },
      });

      // Verify history entry structure
      if (response.body.data.searches.length > 0) {
        const historyEntry = response.body.data.searches[0];
        expect(historyEntry).toMatchObject({
          id: expect.any(String),
          query: expect.any(String),
          searchMode: expect.any(String),
          resultCount: expect.any(Number),
          executionTime: expect.any(Number),
          createdAt: expect.any(String),
        });
      }
    });
  });
});

Database Integration Testing

typescript
// tests/integration/database/database-operations.test.ts
import { DatabaseService } from '@/services/DatabaseService';
import { testDb } from '../../config/database';
import { createTestUser, createTestDatabase } from '../../helpers/auth-helpers';

describe('Database Operations Integration', () => {
  let databaseService: DatabaseService;
  let testUser: any;
  let testDatabaseId: string;

  beforeAll(async () => {
    await testDb.setupTestDatabase();
    databaseService = new DatabaseService();

    testUser = await createTestUser();
    testDatabaseId = await createTestDatabase(testUser.id);
  });

  afterAll(async () => {
    await testDb.cleanupTestDatabase();
  });

  describe('Full-text Search Operations', () => {
    it('should execute natural language search', async () => {
      const results = await databaseService.executeFullTextSearch(
        testDatabaseId,
        'mysql performance optimization',
        ['test_articles'],
        ['title', 'content'],
        10,
        0
      );

      expect(results).toBeInstanceOf(Array);
      expect(results.length).toBeGreaterThan(0);

      // Verify result structure
      const result = results[0];
      expect(result).toHaveProperty('id');
      expect(result).toHaveProperty('title');
      expect(result).toHaveProperty('content');
      expect(result).toHaveProperty('table', 'test_articles');
    });

    it('should execute boolean search', async () => {
      const results = await databaseService.executeFullTextSearch(
        testDatabaseId,
        '+mysql +performance -slow',
        ['test_articles'],
        ['title', 'content'],
        10,
        0,
        'boolean'
      );

      expect(results).toBeInstanceOf(Array);

      // All results should contain 'mysql' and 'performance' but not 'slow'
      results.forEach(result => {
        const text = `${result.title} ${result.content}`.toLowerCase();
        expect(text).toMatch(/mysql/);
        expect(text).toMatch(/performance/);
        expect(text).not.toMatch(/slow/);
      });
    });

    it('should handle empty search results', async () => {
      const results = await databaseService.executeFullTextSearch(
        testDatabaseId,
        'nonexistent query terms xyz123',
        ['test_articles'],
        ['title', 'content'],
        10,
        0
      );

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(0);
    });

    it('should support pagination', async () => {
      // Get first page
      const page1 = await databaseService.executeFullTextSearch(
        testDatabaseId,
        'database',
        ['test_articles'],
        ['title', 'content'],
        2,
        0
      );

      // Get second page
      const page2 = await databaseService.executeFullTextSearch(
        testDatabaseId,
        'database',
        ['test_articles'],
        ['title', 'content'],
        2,
        2
      );

      expect(page1).toBeInstanceOf(Array);
      expect(page2).toBeInstanceOf(Array);

      // Results should be different (if there are enough total results)
      if (page1.length === 2 && page2.length > 0) {
        const page1Ids = page1.map(r => r.id);
        const page2Ids = page2.map(r => r.id);
        expect(page1Ids).not.toEqual(page2Ids);
      }
    });

    it('should handle multiple tables', async () => {
      const results = await databaseService.executeFullTextSearch(
        testDatabaseId,
        'gaming',
        ['test_articles', 'test_products'],
        ['title', 'content', 'name', 'description'],
        10,
        0
      );

      expect(results).toBeInstanceOf(Array);

      // Results should include entries from both tables
      const tables = [...new Set(results.map(r => r.table))];
      expect(tables.length).toBeGreaterThan(0);
    });
  });

  describe('Connection Management', () => {
    it('should test database connection', async () => {
      const connectionConfig = {
        host: 'localhost',
        port: 3306,
        database: 'altus4_test',
        username: process.env.TEST_DB_USER || 'altus4_test',
        password: process.env.TEST_DB_PASSWORD || 'test_password',
      };

      const result = await databaseService.testConnection(connectionConfig);

      expect(result).toMatchObject({
        success: true,
        message: expect.any(String),
        responseTime: expect.any(Number),
      });
    });

    it('should handle connection failures', async () => {
      const invalidConfig = {
        host: 'invalid-host',
        port: 9999,
        database: 'nonexistent',
        username: 'invalid',
        password: 'invalid',
      };

      const result = await databaseService.testConnection(invalidConfig);

      expect(result).toMatchObject({
        success: false,
        error: expect.any(String),
      });
    });
  });

  describe('Schema Discovery', () => {
    it('should discover database schema', async () => {
      const schema = await databaseService.discoverSchema(testDatabaseId);

      expect(schema).toMatchObject({
        database: expect.any(String),
        tables: expect.any(Array),
      });

      // Should include our test tables
      const tableNames = schema.tables.map(t => t.name);
      expect(tableNames).toContain('test_articles');
      expect(tableNames).toContain('test_products');

      // Verify table structure
      const articlesTable = schema.tables.find(t => t.name === 'test_articles');
      expect(articlesTable).toMatchObject({
        name: 'test_articles',
        columns: expect.any(Array),
        fulltextIndexes: expect.any(Array),
        estimatedRows: expect.any(Number),
      });

      // Should have FULLTEXT indexes
      expect(articlesTable.fulltextIndexes.length).toBeGreaterThan(0);
    });
  });
});

Service Integration Testing

Multi-Service Integration

typescript
// tests/integration/services/search-integration.test.ts
import { SearchService } from '@/services/SearchService';
import { DatabaseService } from '@/services/DatabaseService';
import { AIService } from '@/services/AIService';
import { CacheService } from '@/services/CacheService';
import { testDb } from '../../config/database';
import { createTestUser, createTestDatabase } from '../../helpers/auth-helpers';

describe('Search Service Integration', () => {
  let searchService: SearchService;
  let databaseService: DatabaseService;
  let aiService: AIService;
  let cacheService: CacheService;
  let testUser: any;
  let testDatabaseId: string;

  beforeAll(async () => {
    await testDb.setupTestDatabase();

    // Initialize real services
    databaseService = new DatabaseService();
    aiService = new AIService();
    cacheService = new CacheService();
    searchService = new SearchService(databaseService, aiService, cacheService);

    testUser = await createTestUser();
    testDatabaseId = await createTestDatabase(testUser.id);
  });

  afterAll(async () => {
    await testDb.cleanupTestDatabase();
  });

  describe('End-to-End Search Flow', () => {
    it('should execute complete search workflow', async () => {
      const searchRequest = {
        query: 'mysql performance optimization',
        databases: [testDatabaseId],
        userId: testUser.id,
        searchMode: 'natural' as const,
        limit: 10,
      };

      const result = await searchService.search(searchRequest);

      expect(result).toMatchObject({
        results: expect.any(Array),
        totalCount: expect.any(Number),
        executionTime: expect.any(Number),
        categories: expect.any(Array),
        suggestions: expect.any(Array),
      });

      // Verify search results structure
      if (result.results.length > 0) {
        const searchResult = result.results[0];
        expect(searchResult).toMatchObject({
          id: expect.any(String),
          database: testDatabaseId,
          table: expect.any(String),
          data: expect.any(Object),
          relevanceScore: expect.any(Number),
          snippet: expect.any(String),
          matchedColumns: expect.any(Array),
        });
      }
    });

    it('should cache search results', async () => {
      const searchRequest = {
        query: 'database indexing strategies',
        databases: [testDatabaseId],
        userId: testUser.id,
        searchMode: 'natural' as const,
        limit: 10,
      };

      // First search - should hit database
      const startTime1 = Date.now();
      const result1 = await searchService.search(searchRequest);
      const executionTime1 = Date.now() - startTime1;

      // Second search - should hit cache
      const startTime2 = Date.now();
      const result2 = await searchService.search(searchRequest);
      const executionTime2 = Date.now() - startTime2;

      // Results should be identical
      expect(result1.results).toEqual(result2.results);
      expect(result1.totalCount).toBe(result2.totalCount);

      // Second search should be faster (cached)
      expect(executionTime2).toBeLessThan(executionTime1);
    });

    it('should handle AI enhancement when available', async () => {
      // Skip if AI service is not available
      if (!aiService.isAvailable()) {
        console.log('Skipping AI test - service not available');
        return;
      }

      const searchRequest = {
        query: 'improve database speed',
        databases: [testDatabaseId],
        userId: testUser.id,
        searchMode: 'semantic' as const,
        limit: 10,
      };

      const result = await searchService.search(searchRequest);

      expect(result).toMatchObject({
        results: expect.any(Array),
        totalCount: expect.any(Number),
        executionTime: expect.any(Number),
      });

      // AI-enhanced search might have categories
      if (result.categories && result.categories.length > 0) {
        expect(result.categories[0]).toMatchObject({
          name: expect.any(String),
          count: expect.any(Number),
        });
      }
    });

    it('should handle multiple database search', async () => {
      // Create second test database
      const testDatabase2Id = await createTestDatabase(testUser.id, {
        name: 'Test Database 2',
      });

      const searchRequest = {
        query: 'database performance',
        databases: [testDatabaseId, testDatabase2Id],
        userId: testUser.id,
        searchMode: 'natural' as const,
        limit: 20,
      };

      const result = await searchService.search(searchRequest);

      expect(result).toMatchObject({
        results: expect.any(Array),
        totalCount: expect.any(Number),
        executionTime: expect.any(Number),
      });

      // Results should potentially come from both databases
      if (result.results.length > 0) {
        const databases = [...new Set(result.results.map(r => r.database))];
        expect(databases.length).toBeGreaterThan(0);
      }
    });

    it('should handle database failures gracefully', async () => {
      // Create database with invalid configuration
      const invalidDatabaseId = await createTestDatabase(testUser.id, {
        name: 'Invalid Database',
        host: 'invalid-host',
        port: 9999,
      });

      const searchRequest = {
        query: 'test query',
        databases: [testDatabaseId, invalidDatabaseId],
        userId: testUser.id,
        searchMode: 'natural' as const,
        limit: 10,
      };

      // Should not throw error, but handle failure gracefully
      const result = await searchService.search(searchRequest);

      expect(result).toMatchObject({
        results: expect.any(Array),
        totalCount: expect.any(Number),
        executionTime: expect.any(Number),
      });

      // Should still have results from valid database
      if (result.results.length > 0) {
        result.results.forEach(r => {
          expect(r.database).toBe(testDatabaseId);
        });
      }
    });
  });

  describe('Search Analytics Integration', () => {
    it('should log search analytics', async () => {
      const searchRequest = {
        query: 'analytics test query',
        databases: [testDatabaseId],
        userId: testUser.id,
        searchMode: 'natural' as const,
        limit: 10,
      };

      await searchService.search(searchRequest);

      // Verify analytics were logged
      const connection = await testDb.getConnection();
      const [searches] = await connection.execute(
        'SELECT * FROM searches WHERE user_id = ? AND query = ?',
        [testUser.id, searchRequest.query]
      );

      expect(searches).toHaveLength(1);
      const searchLog = (searches as any[])[0];
      expect(searchLog).toMatchObject({
        user_id: testUser.id,
        query: searchRequest.query,
        search_mode: searchRequest.searchMode,
        result_count: expect.any(Number),
        execution_time_ms: expect.any(Number),
      });
    });
  });
});

Performance Integration Testing

typescript
// tests/integration/performance/api-performance.test.ts
import request from 'supertest';
import { TestServer } from '../../helpers/test-server';
import {
  createTestUser,
  createTestApiKey,
  createTestDatabase,
} from '../../helpers/auth-helpers';
import { performance } from 'perf_hooks';

describe('API Performance Integration', () => {
  let testServer: TestServer;
  let baseUrl: string;
  let testApiKey: string;
  let testDatabaseId: string;

  beforeAll(async () => {
    testServer = new TestServer();
    await testServer.start();
    baseUrl = testServer.getBaseUrl();

    const testUser = await createTestUser();
    testApiKey = await createTestApiKey(testUser.id);
    testDatabaseId = await createTestDatabase(testUser.id);
  });

  afterAll(async () => {
    await testServer.stop();
  });

  describe('Search Performance', () => {
    it('should handle concurrent search requests', async () => {
      const concurrentRequests = 10;
      const searchPromises = Array.from(
        { length: concurrentRequests },
        (_, i) =>
          request(baseUrl)
            .post('/api/v1/search')
            .set('Authorization', `Bearer ${testApiKey}`)
            .send({
              query: `performance test query ${i}`,
              databases: [testDatabaseId],
              limit: 10,
            })
      );

      const startTime = performance.now();
      const results = await Promise.all(searchPromises);
      const endTime = performance.now();

      const totalTime = endTime - startTime;
      const averageTime = totalTime / concurrentRequests;

      // All requests should succeed
      results.forEach(response => {
        expect(response.status).toBe(200);
        expect(response.body.success).toBe(true);
      });

      // Performance assertions
      expect(averageTime).toBeLessThan(1000); // Average < 1 second
      expect(totalTime).toBeLessThan(5000); // Total < 5 seconds
    });

    it('should maintain performance under load', async () => {
      const requestCount = 50;
      const batchSize = 10;
      const batches = Math.ceil(requestCount / batchSize);

      const allResults = [];
      const executionTimes = [];

      for (let batch = 0; batch < batches; batch++) {
        const batchPromises = Array.from({ length: batchSize }, (_, i) => {
          const startTime = performance.now();

          return request(baseUrl)
            .post('/api/v1/search')
            .set('Authorization', `Bearer ${testApiKey}`)
            .send({
              query: 'load test query',
              databases: [testDatabaseId],
              limit: 10,
            })
            .then(response => {
              const endTime = performance.now();
              executionTimes.push(endTime - startTime);
              return response;
            });
        });

        const batchResults = await Promise.all(batchPromises);
        allResults.push(...batchResults);

        // Small delay between batches
        await new Promise(resolve => setTimeout(resolve, 100));
      }

      // All requests should succeed
      const successfulRequests = allResults.filter(
        r => r.status === 200
      ).length;
      const successRate = (successfulRequests / requestCount) * 100;

      expect(successRate).toBeGreaterThan(95); // 95% success rate

      // Performance metrics
      const averageTime =
        executionTimes.reduce((a, b) => a + b, 0) / executionTimes.length;
      const p95Time = executionTimes.sort((a, b) => a - b)[
        Math.floor(executionTimes.length * 0.95)
      ];

      expect(averageTime).toBeLessThan(500); // Average < 500ms
      expect(p95Time).toBeLessThan(1000); // P95 < 1 second
    });
  });
});

Test Configuration

Jest Integration Config

javascript
// jest.integration.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/tests/integration'],
  testMatch: ['<rootDir>/tests/integration/**/*.test.ts'],
  setupFilesAfterEnv: ['<rootDir>/tests/integration-setup.ts'],
  testTimeout: 30000, // 30 seconds for integration tests
  maxWorkers: 1, // Run tests sequentially to avoid database conflicts
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/**/__tests__/**',
  ],
  coverageDirectory: 'coverage/integration',
  globalSetup: '<rootDir>/tests/global-setup.ts',
  globalTeardown: '<rootDir>/tests/global-teardown.ts',
};

Integration Test Setup

typescript
// tests/integration-setup.ts
import { testDb } from './config/database';

// Global setup for integration tests
beforeAll(async () => {
  // Setup test database
  await testDb.setupTestDatabase();

  // Set environment variables
  process.env.NODE_ENV = 'test';
  process.env.LOG_LEVEL = 'error';
  process.env.CACHE_ENABLED = 'true';
  process.env.AI_ENABLED = 'false'; // Disable AI for most tests
}, 60000); // 60 second timeout

afterAll(async () => {
  // Cleanup test database
  await testDb.cleanupTestDatabase();
}, 30000);

// Clean up between tests
afterEach(async () => {
  // Clear cache
  if (global.cacheService) {
    await global.cacheService.flushAll();
  }
});

Best Practices

Integration Test Guidelines

  1. Use Real Dependencies: Test with actual database, cache, and external services
  2. Isolate Tests: Each test should be independent and not affect others
  3. Test Happy Path: Verify normal operation flows
  4. Test Error Conditions: Verify error handling and recovery
  5. Performance Aware: Monitor test execution time and system resources
  6. Data Management: Use fixtures and cleanup between tests
  7. Environment Consistency: Use consistent test environment setup

Common Patterns

typescript
// Pattern: Test setup and cleanup
describe('Feature Integration', () => {
  let testData: any;

  beforeEach(async () => {
    testData = await setupTestData();
  });

  afterEach(async () => {
    await cleanupTestData(testData);
  });

  it('should work correctly', async () => {
    // Test implementation
  });
});

// Pattern: Error handling verification
it('should handle service failures gracefully', async () => {
  // Simulate service failure
  jest
    .spyOn(externalService, 'method')
    .mockRejectedValue(new Error('Service down'));

  // Verify graceful handling
  const result = await serviceUnderTest.operation();
  expect(result.success).toBe(false);
  expect(result.error).toBeDefined();
});

// Pattern: Performance assertions
it('should meet performance requirements', async () => {
  const startTime = performance.now();

  await operationUnderTest();

  const executionTime = performance.now() - startTime;
  expect(executionTime).toBeLessThan(1000); // < 1 second
});

Integration tests ensure that Altus 4 components work correctly together in realistic scenarios. Follow these patterns to build comprehensive integration test suites that verify system behavior and catch integration issues early.