BREAKING CHANGE: Application now uses config.yaml instead of .env files ## Changes: - Remove dotenv dependency from package.json - Update all services to use ConfigManager for configuration - Update tests to use fixed credentials instead of env vars - Delete obsolete config-manager.test.ts (used old .env API) - Update documentation (README.md, CLAUDE.md) to reflect new config system ## Configuration Architecture: - ConfigManager: Centralized YAML configuration with Zod validation - config.yaml location: - Development: Project root (easy to edit and version control) - Production: User AppData (persists across updates) - ERP credentials: Stored in database (dbo_BIPUsers) per user - Other settings: Stored in config.yaml (database, paths, extraction, etc.) ## Files Modified: - package.json: Removed dotenv dependency - cleaner-handler.ts: Use ConfigManager.getDatabaseType() - run-migration.ts: Read from config.yaml instead of .env - All integration tests: Use fixed test credentials - tests/setup.ts: Removed dotenv loading - README.md, CLAUDE.md: Updated documentation Migration is complete. Application no longer depends on .env files.
271 lines
7.2 KiB
TypeScript
271 lines
7.2 KiB
TypeScript
/**
|
|
* 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)
|
|
})
|
|
})
|
|
})
|