🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
Módulo 4 - Aula 2

Testes de Integração

Domine testes de integração para tRPC: database testing com containers, API testing end-to-end e automação completa para SaaS enterprise-grade.

85 min
Avançado
Integration

🎯 Por que testes de integração são essenciais?

Confiança Total: Validam que todos os componentes funcionam corretamente em conjunto no ambiente real.

Detecção Precoce: Identificam problemas de compatibilidade entre serviços antes do deploy em produção.

🐳 Setup com Docker e Test Containers

docker-compose.test.yml
// 📁 docker-compose.test.yml
version: '3.8'

services:
  # 🗄️ PostgreSQL para testes
  postgres-test:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: trpc_test
      POSTGRES_USER: test_user
      POSTGRES_PASSWORD: test_password
    ports:
      - "5433:5432"
    volumes:
      - postgres_test_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test_user -d trpc_test"]
      interval: 5s
      timeout: 5s
      retries: 5

  # 🔴 Redis para cache e sessions
  redis-test:
    image: redis:7-alpine
    ports:
      - "6380:6379"
    command: redis-server --appendonly yes
    volumes:
      - redis_test_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  # 📧 MailHog para testes de email
  mailhog-test:
    image: mailhog/mailhog:latest
    ports:
      - "1026:1025"  # SMTP
      - "8026:8025"  # Web UI
    
volumes:
  postgres_test_data:
  redis_test_data:

// 📁 src/test/setup-integration.ts
import { beforeAll, afterAll } from 'vitest';
import { GenericContainer, StartedTestContainer } from 'testcontainers';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

let postgresContainer: StartedTestContainer;
let redisContainer: StartedTestContainer;

// 🚀 Setup de containers para testes
beforeAll(async () => {
  console.log('🐳 Iniciando containers de teste...');
  
  // 🗄️ PostgreSQL container
  postgresContainer = await new GenericContainer('postgres:15-alpine')
    .withEnvironment({
      POSTGRES_DB: 'trpc_test',
      POSTGRES_USER: 'test_user',
      POSTGRES_PASSWORD: 'test_password',
    })
    .withExposedPorts(5432)
    .withHealthCheck({
      test: ['CMD-SHELL', 'pg_isready -U test_user -d trpc_test'],
      interval: 5000,
      timeout: 5000,
      retries: 5,
    })
    .start();

  // 🔴 Redis container
  redisContainer = await new GenericContainer('redis:7-alpine')
    .withExposedPorts(6379)
    .withHealthCheck({
      test: ['CMD', 'redis-cli', 'ping'],
      interval: 5000,
      timeout: 3000,
      retries: 5,
    })
    .start();

  // 🔧 Configurar variáveis de ambiente
  const postgresPort = postgresContainer.getMappedPort(5432);
  const redisPort = redisContainer.getMappedPort(6379);
  
  process.env.DATABASE_URL = `postgresql://test_user:test_password@localhost:${postgresPort}/trpc_test`;
  process.env.REDIS_URL = `redis://localhost:${redisPort}`;
  
  // 📊 Executar migrações do Prisma
  console.log('📊 Executando migrações...');
  await execAsync('npx prisma migrate deploy');
  
  console.log('✅ Containers iniciados e banco configurado');
}, 60000);

// 🧹 Cleanup de containers
afterAll(async () => {
  console.log('🧹 Limpando containers...');
  
  await postgresContainer?.stop();
  await redisContainer?.stop();
  
  console.log('✅ Containers removidos');
}, 30000);

🗄️ Database Integration Testing

src/test/integration/database.test.ts
// 📁 src/test/integration/database.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { PrismaClient } from '@prisma/client';
import { createTestCallerWithContext } from '@/test/helpers/trpc-test-utils';

// 🔧 Instância real do Prisma para testes de integração
const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL,
    },
  },
});

describe('Database Integration Tests', () => {
  // 🧹 Limpar dados entre testes
  beforeEach(async () => {
    await prisma.user.deleteMany();
    await prisma.organization.deleteMany();
    await prisma.permission.deleteMany();
  });

  afterEach(async () => {
    await prisma.user.deleteMany();
    await prisma.organization.deleteMany();
    await prisma.permission.deleteMany();
  });

  describe('User Operations', () => {
    it('🔄 deve criar usuário com organização e permissões', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({
        prisma, // Usar Prisma real
      });

      const userData = {
        email: 'integration@test.com',
        name: 'Integration User',
        organizationName: 'Test Organization',
        plan: 'PRO' as const,
      };

      // 🎬 Act
      const result = await caller.user.createWithOrganization(userData);

      // ✅ Assert - Verificar no banco real
      const createdUser = await prisma.user.findUnique({
        where: { id: result.userId },
        include: {
          organization: {
            include: {
              plan: true,
            },
          },
          permissions: true,
        },
      });

      expect(createdUser).toBeTruthy();
      expect(createdUser?.email).toBe(userData.email);
      expect(createdUser?.organization?.name).toBe(userData.organizationName);
      expect(createdUser?.organization?.plan.name).toBe(userData.plan);
      expect(createdUser?.permissions).toHaveLength(3); // Permissões padrão
    });

    it('🔒 deve validar constraints de unicidade', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({ prisma });
      
      // Criar primeiro usuário
      await prisma.user.create({
        data: {
          email: 'duplicate@test.com',
          name: 'First User',
          password: 'hashed-password',
        },
      });

      // 🎬 Act & Assert
      await expect(
        caller.user.register({
          email: 'duplicate@test.com', // Email duplicado
          name: 'Second User',
          password: 'password123',
        })
      ).rejects.toThrow('Email já está em uso');
    });

    it('🔄 deve executar transação completa ou rollback', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({ prisma });
      
      // Mock para falhar na criação de permissões
      vi.spyOn(prisma.permission, 'createMany').mockRejectedValueOnce(
        new Error('Falha na criação de permissões')
      );

      // 🎬 Act & Assert
      await expect(
        caller.user.createWithOrganization({
          email: 'transaction@test.com',
          name: 'Transaction User',
          organizationName: 'Failed Org',
          plan: 'FREE',
        })
      ).rejects.toThrow();

      // ✅ Verificar que nada foi criado (rollback)
      const users = await prisma.user.findMany();
      const organizations = await prisma.organization.findMany();
      
      expect(users).toHaveLength(0);
      expect(organizations).toHaveLength(0);
    });
  });

  describe('Complex Queries', () => {
    it('📊 deve executar queries agregadas complexas', async () => {
      // 🎯 Arrange - Criar dados de teste
      const org = await prisma.organization.create({
        data: {
          name: 'Analytics Org',
          plan: { create: { name: 'PRO', apiCallsLimit: 10000 } },
        },
      });

      // Criar múltiplos usuários
      await prisma.user.createMany({
        data: Array.from({ length: 10 }, (_, i) => ({
          email: `user${i}@analytics.com`,
          name: `User ${i}`,
          password: 'password',
          organizationId: org.id,
        })),
      });

      // Criar logs de API
      const users = await prisma.user.findMany();
      await prisma.apiLog.createMany({
        data: users.flatMap(user =>
          Array.from({ length: 50 }, (_, i) => ({
            userId: user.id,
            endpoint: `/api/endpoint${i % 5}`,
            method: 'GET',
            statusCode: i % 10 === 0 ? 500 : 200,
            responseTime: Math.floor(Math.random() * 1000),
            timestamp: new Date(Date.now() - i * 60000),
          }))
        ),
      });

      const caller = createTestCallerWithContext({ prisma });

      // 🎬 Act
      const analytics = await caller.organization.getDetailedAnalytics({
        organizationId: org.id,
        period: '7d',
      });

      // ✅ Assert
      expect(analytics).toMatchObject({
        totalUsers: 10,
        totalApiCalls: 500,
        averageResponseTime: expect.any(Number),
        errorRate: expect.any(Number),
        topEndpoints: expect.arrayContaining([
          expect.objectContaining({
            endpoint: expect.stringMatching(/^/api/endpointd$/),
            calls: expect.any(Number),
            percentage: expect.any(Number),
          }),
        ]),
        userActivity: expect.arrayContaining([
          expect.objectContaining({
            userId: expect.any(String),
            apiCalls: expect.any(Number),
            lastActivity: expect.any(Date),
          }),
        ]),
      });
    });
  });
});

🌐 API Integration Testing

src/test/integration/api.test.ts
// 📁 src/test/integration/api.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createServer } from 'http';
import { parse } from 'url';
import next from 'next';
import { createTRPCMsw } from 'msw-trpc';
import { setupServer } from 'msw/node';
import { appRouter } from '@/server/trpc/router';

// 🚀 Setup do servidor Next.js para testes
const app = next({ dev: false, quiet: true });
const handle = app.getRequestHandler();

let server: any;
let baseURL: string;

beforeAll(async () => {
  await app.prepare();
  
  // 🔧 Criar servidor HTTP
  server = createServer((req, res) => {
    const parsedUrl = parse(req.url!, true);
    handle(req, res, parsedUrl);
  });
  
  // 🎯 Iniciar em porta aleatória
  await new Promise<void>((resolve) => {
    server.listen(0, () => {
      const port = server.address().port;
      baseURL = `http://localhost:${port}`;
      resolve();
    });
  });
}, 30000);

afterAll(async () => {
  if (server) {
    server.close();
  }
  await app.close();
});

describe('API Integration Tests', () => {
  describe('tRPC HTTP Endpoints', () => {
    it('🔍 deve responder a query via HTTP GET', async () => {
      // 🎯 Arrange
      const response = await fetch(
        `${baseURL}/api/trpc/user.getProfile?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%7D`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
            Authorization: 'Bearer valid-jwt-token',
          },
        }
      );

      // ✅ Assert
      expect(response.status).toBe(200);
      
      const data = await response.json();
      expect(data).toHaveProperty('0.result.data');
      expect(data[0].result.data).toMatchObject({
        id: expect.any(String),
        email: expect.any(String),
        name: expect.any(String),
      });
    });

    it('📝 deve processar mutation via HTTP POST', async () => {
      // 🎯 Arrange
      const updateData = {
        name: 'Updated Name',
        email: 'updated@example.com',
      };

      const response = await fetch(
        `${baseURL}/api/trpc/user.updateProfile`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: 'Bearer valid-jwt-token',
          },
          body: JSON.stringify({
            0: { json: updateData },
          }),
        }
      );

      // ✅ Assert
      expect(response.status).toBe(200);
      
      const data = await response.json();
      expect(data[0].result.data).toMatchObject({
        id: expect.any(String),
        name: updateData.name,
        email: updateData.email,
        updatedAt: expect.any(String),
      });
    });

    it('🚫 deve retornar erro 401 para requisições não autenticadas', async () => {
      // 🎯 Arrange & Act
      const response = await fetch(
        `${baseURL}/api/trpc/user.getProfile?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%7D`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
            // Sem Authorization header
          },
        }
      );

      // ✅ Assert
      expect(response.status).toBe(401);
      
      const data = await response.json();
      expect(data[0].error).toMatchObject({
        code: -32001, // tRPC UNAUTHORIZED
        message: expect.stringContaining('logado'),
      });
    });

    it('🔄 deve processar batch requests corretamente', async () => {
      // 🎯 Arrange
      const batchInput = {
        0: { json: null }, // getProfile
        1: { json: { period: '30d' } }, // getAnalytics
      };

      const response = await fetch(
        `${baseURL}/api/trpc/user.getProfile,organization.getAnalytics?batch=1&input=${encodeURIComponent(JSON.stringify(batchInput))}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
            Authorization: 'Bearer valid-jwt-token',
          },
        }
      );

      // ✅ Assert
      expect(response.status).toBe(200);
      
      const data = await response.json();
      expect(data).toHaveLength(2);
      expect(data[0]).toHaveProperty('result.data');
      expect(data[1]).toHaveProperty('result.data');
    });
  });

  describe('WebSocket Integration', () => {
    it('📡 deve estabelecer conexão WebSocket para subscriptions', async () => {
      // 🎯 Arrange
      const ws = new WebSocket(`ws://localhost:${baseURL.split(':')[2]}/api/trpc`);
      
      const messages: any[] = [];
      
      ws.onmessage = (event) => {
        messages.push(JSON.parse(event.data));
      };

      // 🔄 Aguardar conexão
      await new Promise((resolve) => {
        ws.onopen = resolve;
      });

      // 🎬 Act - Enviar subscription
      ws.send(JSON.stringify({
        id: 1,
        method: 'subscription',
        params: {
          path: 'notifications.onUserUpdate',
          input: null,
        },
      }));

      // Simular evento que deve disparar a subscription
      await fetch(`${baseURL}/api/trpc/user.updateProfile`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'Bearer valid-jwt-token',
        },
        body: JSON.stringify({
          0: { json: { name: 'WebSocket Test' } },
        }),
      });

      // ⏰ Aguardar mensagem
      await new Promise(resolve => setTimeout(resolve, 1000));

      // ✅ Assert
      expect(messages).toContainEqual(
        expect.objectContaining({
          id: 1,
          result: expect.objectContaining({
            type: 'data',
            data: expect.objectContaining({
              userId: expect.any(String),
              action: 'profile_updated',
            }),
          }),
        })
      );

      ws.close();
    });
  });
});

🔴 Redis Integration Testing

src/test/integration/redis.test.ts
// 📁 src/test/integration/redis.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Redis from 'ioredis';
import { createTestCallerWithContext } from '@/test/helpers/trpc-test-utils';

// 🔴 Instância real do Redis para testes
const redis = new Redis(process.env.REDIS_URL!);

describe('Redis Integration Tests', () => {
  // 🧹 Limpar Redis entre testes
  beforeEach(async () => {
    await redis.flushdb();
  });

  afterEach(async () => {
    await redis.flushdb();
  });

  describe('Cache Operations', () => {
    it('💾 deve cachear resultados de queries pesadas', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({ redis });
      
      // 🎬 Act - Primeira chamada (deve ir ao banco)
      const start1 = Date.now();
      const result1 = await caller.analytics.getComplexReport({
        organizationId: 'test-org',
        period: '30d',
      });
      const duration1 = Date.now() - start1;

      // 🎬 Act - Segunda chamada (deve vir do cache)
      const start2 = Date.now();
      const result2 = await caller.analytics.getComplexReport({
        organizationId: 'test-org',
        period: '30d',
      });
      const duration2 = Date.now() - start2;

      // ✅ Assert
      expect(result1).toEqual(result2);
      expect(duration2).toBeLessThan(duration1 * 0.1); // Cache 10x mais rápido
      
      // Verificar que foi armazenado no Redis
      const cacheKey = 'analytics:complex-report:test-org:30d';
      const cachedData = await redis.get(cacheKey);
      expect(cachedData).toBeTruthy();
      expect(JSON.parse(cachedData!)).toEqual(result1);
    });

    it('⏰ deve respeitar TTL do cache', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({ redis });
      const cacheKey = 'test:ttl-key';
      
      // 🎬 Act - Definir cache com TTL de 2 segundos
      await redis.setex(cacheKey, 2, JSON.stringify({ data: 'test' }));
      
      // ✅ Assert - Verificar que existe
      const cached1 = await redis.get(cacheKey);
      expect(cached1).toBeTruthy();
      
      // ⏰ Aguardar expiração
      await new Promise(resolve => setTimeout(resolve, 2100));
      
      // ✅ Assert - Verificar que expirou
      const cached2 = await redis.get(cacheKey);
      expect(cached2).toBeNull();
    });

    it('🔄 deve invalidar cache automaticamente', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({ redis });
      const userId = 'test-user-id';
      
      // Cachear perfil do usuário
      await caller.user.getProfile(); // Primeira chamada para cachear
      
      const profileCacheKey = `user:profile:${userId}`;
      const cachedProfile = await redis.get(profileCacheKey);
      expect(cachedProfile).toBeTruthy();

      // 🎬 Act - Atualizar perfil (deve invalidar cache)
      await caller.user.updateProfile({
        name: 'Updated Name',
      });

      // ✅ Assert - Verificar que cache foi invalidado
      const invalidatedCache = await redis.get(profileCacheKey);
      expect(invalidatedCache).toBeNull();
    });
  });

  describe('Rate Limiting', () => {
    it('🚫 deve aplicar rate limiting corretamente', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({ redis });
      const userId = 'rate-limit-user';
      
      // 🎬 Act - Fazer 100 requisições (limite)
      const promises = Array.from({ length: 100 }, () =>
        caller.user.getProfile().catch(e => e)
      );
      
      const results = await Promise.all(promises);
      const successes = results.filter(r => !r.code);
      const rateLimitErrors = results.filter(r => r.code === 'TOO_MANY_REQUESTS');

      // ✅ Assert
      expect(successes).toHaveLength(100); // Todas as primeiras 100 passam
      
      // 🎬 Act - Tentar mais uma (deve falhar)
      await expect(caller.user.getProfile()).rejects.toThrow('TOO_MANY_REQUESTS');
      
      // Verificar contador no Redis
      const rateLimitKey = `rate_limit:${userId}`;
      const count = await redis.zcard(rateLimitKey);
      expect(count).toBe(100);
    });

    it('🔄 deve resetar rate limit após janela', async () => {
      // 🎯 Arrange
      const caller = createTestCallerWithContext({ redis });
      const rateLimitKey = 'rate_limit:reset-test';
      
      // Simular rate limit atingido
      const now = Date.now();
      await redis.zadd(rateLimitKey, now, `${now}-1`);
      await redis.zadd(rateLimitKey, now + 1, `${now + 1}-2`);
      await redis.expire(rateLimitKey, 1); // Expira em 1 segundo

      // 🎬 Act - Aguardar reset
      await new Promise(resolve => setTimeout(resolve, 1100));
      
      // ✅ Assert - Rate limit deve ter resetado
      const count = await redis.zcard(rateLimitKey);
      expect(count).toBe(0);
      
      // Deve permitir novas requisições
      await expect(caller.user.getProfile()).resolves.toBeTruthy();
    });
  });

  describe('Session Management', () => {
    it('🔐 deve gerenciar sessões no Redis', async () => {
      // 🎯 Arrange
      const sessionId = 'test-session-id';
      const sessionData = {
        userId: 'test-user',
        organizationId: 'test-org',
        role: 'USER',
        createdAt: Date.now(),
      };

      // 🎬 Act - Criar sessão
      await redis.setex(
        `session:${sessionId}`,
        3600, // 1 hora
        JSON.stringify(sessionData)
      );

      // ✅ Assert - Verificar sessão criada
      const storedSession = await redis.get(`session:${sessionId}`);
      expect(JSON.parse(storedSession!)).toEqual(sessionData);
      
      // 🎬 Act - Simular logout (deletar sessão)
      await redis.del(`session:${sessionId}`);
      
      // ✅ Assert - Verificar sessão removida
      const deletedSession = await redis.get(`session:${sessionId}`);
      expect(deletedSession).toBeNull();
    });

    it('🔄 deve renovar sessões automaticamente', async () => {
      // 🎯 Arrange
      const sessionId = 'renewal-test-session';
      
      // Criar sessão com TTL curto
      await redis.setex(
        `session:${sessionId}`,
        2, // 2 segundos
        JSON.stringify({ userId: 'test' })
      );

      // Verificar TTL inicial
      const initialTTL = await redis.ttl(`session:${sessionId}`);
      expect(initialTTL).toBeLessThanOrEqual(2);

      // 🎬 Act - Renovar sessão
      await redis.expire(`session:${sessionId}`, 10); // Renovar para 10 segundos

      // ✅ Assert - Verificar TTL renovado
      const renewedTTL = await redis.ttl(`session:${sessionId}`);
      expect(renewedTTL).toBeGreaterThan(5);
    });
  });
});

🚀 CI/CD e Automação

.github/workflows/integration-tests.yml
// 📁 .github/workflows/integration-tests.yml
name: Integration Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  integration-tests:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: trpc_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      
      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Setup test database
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/trpc_test
        run: |
          npx prisma migrate deploy
          npx prisma db seed

      - name: Run integration tests
        env:
          NODE_ENV: test
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/trpc_test
          REDIS_URL: redis://localhost:6379
          JWT_SECRET: test-secret-for-ci
        run: npm run test:integration

      - name: Upload test results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: integration-test-results
          path: |
            test-results/
            coverage/

// 📁 scripts/test-setup.sh
#!/bin/bash

# 🎯 Script para setup completo de ambiente de teste

echo "🐳 Iniciando containers de teste..."

# Parar containers existentes
docker-compose -f docker-compose.test.yml down

# Iniciar containers frescos
docker-compose -f docker-compose.test.yml up -d

# Aguardar containers ficarem healthy
echo "⏰ Aguardando containers ficarem prontos..."
timeout 60s bash -c 'until docker-compose -f docker-compose.test.yml ps | grep healthy; do sleep 2; done'

# Executar migrações
echo "📊 Executando migrações..."
DATABASE_URL="postgresql://test_user:test_password@localhost:5433/trpc_test" npx prisma migrate deploy

# Executar seeds
echo "🌱 Executando seeds..."
DATABASE_URL="postgresql://test_user:test_password@localhost:5433/trpc_test" npx prisma db seed

echo "✅ Ambiente de teste pronto!"

# 📁 scripts/test-cleanup.sh
#!/bin/bash

echo "🧹 Limpando ambiente de teste..."

# Parar e remover containers
docker-compose -f docker-compose.test.yml down -v

# Remover volumes órfãos
docker volume prune -f

# Limpar cache de testes
rm -rf .vitest
rm -rf coverage
rm -rf test-results

echo "✅ Limpeza concluída!"

// 📁 vitest.integration.config.ts
import { defineConfig } from 'vitest/config';
import { resolve } from 'path';

export default defineConfig({
  test: {
    // 🎯 Configurações específicas para integração
    name: 'integration',
    include: ['src/test/integration/**/*.test.ts'],
    setupFiles: ['./src/test/setup-integration.ts'],
    
    // ⏰ Timeouts maiores para testes de integração
    testTimeout: 30000,
    hookTimeout: 60000,
    
    // 🔄 Executar sequencialmente para evitar conflitos
    pool: 'forks',
    poolOptions: {
      forks: {
        singleFork: true,
      },
    },
    
    // 📊 Configuração de coverage
    coverage: {
      provider: 'v8',
      include: ['src/server/**'],
      exclude: [
        'src/test/**',
        'src/server/**/*.test.ts',
        'src/server/**/*.spec.ts',
      ],
      threshold: {
        global: {
          branches: 70,
          functions: 70,
          lines: 70,
          statements: 70,
        },
      },
    },
  },
  
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
    },
  },
});

💡 Melhores Práticas para Testes de Integração

Ambiente Isolado:Use containers para garantir ambiente consistente e isolado.

Dados Realistas:Use dados que simulem cenários reais de produção.

Cleanup Rigoroso:Sempre limpe estado entre testes para evitar interferências.

Timeouts Adequados:Configure timeouts maiores para operações de I/O reais.