/** * Integration tests for MySqlService * These tests require a running MySQL instance * Skip if MySQL is not available: npx vitest run --reporter=verbose */ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { MySqlService, MySqlConfig } from '@services/database/mysql' // MySQL test configuration // For integration tests, use fixed test credentials or configure via config.yaml const testConfig: MySqlConfig = { host: 'localhost', port: 3306, user: 'root', password: 'password', database: 'test_db' } describe('MySqlService Integration Tests', () => { let service: MySqlService let isMysqlAvailable = true beforeAll(async () => { service = new MySqlService(testConfig) // Try to connect, skip tests if MySQL is not available try { await service.connect() } catch (error) { console.warn('MySQL not available, skipping integration tests') isMysqlAvailable = false } }) afterAll(async () => { if (service && isMysqlAvailable) { await service.disconnect() } }) describe('connect()', () => { it('should connect to MySQL database', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } expect(service.isConnected()).toBe(true) }) it('should throw error when already connected', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } // Already connected in beforeAll await expect(service.connect()).rejects.toThrow('Already connected') }) }) describe('isConnected()', () => { it('should return true when connected', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } expect(service.isConnected()).toBe(true) }) it('should return false when not connected', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } const newService = new MySqlService(testConfig) expect(newService.isConnected()).toBe(false) }) }) describe('disconnect()', () => { it('should disconnect from MySQL database', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } await service.disconnect() expect(service.isConnected()).toBe(false) // Reconnect for other tests await service.connect() }) it('should not throw when already disconnected', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } const newService = new MySqlService(testConfig) await expect(newService.disconnect()).resolves.not.toThrow() }) }) describe('query()', () => { beforeAll(async () => { if (!isMysqlAvailable) { return } // Ensure connected if (!service.isConnected()) { await service.connect() } // Create test table await service.query(` CREATE TABLE IF NOT EXISTS test_users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `) // Clean up test data await service.query('DELETE FROM test_users WHERE email LIKE ?', ['%test%']) }) afterAll(async () => { if (!isMysqlAvailable) { return } // Drop test table try { await service.query('DROP TABLE IF EXISTS test_users') } catch { // Ignore cleanup errors } }) it('should execute SELECT 1', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } const result = await service.query('SELECT 1 as value') expect(result.columns).toContain('value') expect(result.rowCount).toBe(1) expect(result.rows[0]).toEqual({ value: 1 }) }) it('should execute INSERT with parameters', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } const result = await service.query('INSERT INTO test_users (name, email) VALUES (?, ?)', [ 'Test User', 'test@example.com' ]) expect(result.rowCount).toBeGreaterThanOrEqual(0) }) it('should execute SELECT with parameters', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } const result = await service.query('SELECT id, name, email FROM test_users WHERE email = ?', [ 'test@example.com' ]) expect(result.columns).toEqual(['id', 'name', 'email']) expect(result.rowCount).toBeGreaterThanOrEqual(0) expect(result.rows[0]?.name).toBe('Test User') }) it('should throw error when not connected', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } const newService = new MySqlService(testConfig) await expect(newService.query('SELECT 1')).rejects.toThrow('Not connected') }) }) describe('transaction()', () => { beforeAll(async () => { if (!isMysqlAvailable) { return } // Ensure connected and table exists if (!service.isConnected()) { await service.connect() } await service.query(` CREATE TABLE IF NOT EXISTS test_accounts ( id INT AUTO_INCREMENT PRIMARY KEY, balance DECIMAL(10, 2) NOT NULL DEFAULT 0 ) `) // Clean up and initialize await service.query('DELETE FROM test_accounts') await service.query('INSERT INTO test_accounts (balance) VALUES (100), (50)') }) afterAll(async () => { if (!isMysqlAvailable) { return } try { await service.query('DROP TABLE IF EXISTS test_accounts') } catch { // Ignore cleanup errors } }) it('should execute multiple queries in a transaction', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } // Transfer 20 from account 1 to account 2 await service.transaction([ { sql: 'UPDATE test_accounts SET balance = balance - ? WHERE id = ?', params: [20, 1] }, { sql: 'UPDATE test_accounts SET balance = balance + ? WHERE id = ?', params: [20, 2] } ]) const result = await service.query('SELECT balance FROM test_accounts ORDER BY id') expect(result.rows[0]?.balance).toEqual(expect.anything()) expect(result.rows[1]?.balance).toEqual(expect.anything()) }) it('should rollback on error', async () => { if (!isMysqlAvailable) { console.log('Skipped: MySQL not available') return } // Get initial balances const initial = await service.query('SELECT balance FROM test_accounts ORDER BY id') // Try invalid transaction (syntax error) await expect(service.transaction([{ sql: 'INVALID SQL STATEMENT' }])).rejects.toThrow() // Verify balances unchanged const result = await service.query('SELECT balance FROM test_accounts ORDER BY id') expect(result.rows).toEqual(initial.rows) }) }) })