godcrm/backend/services/__tests__/DataSourceService.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

338 lines
10 KiB
JavaScript

// 🔴 RED Phase: Tests for DataSourceService
// Test-Driven Development: Write tests FIRST
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import { setupTestDatabase, cleanupTestDatabase } from '../../tests/helpers/test-db.js';
import DataSourceService from '../DataSourceService.js';
// Set test mode
process.env.TEST_MODE = 'true';
process.env.SKIP_DEV_USER = 'true';
process.env.MASTER_ENCRYPTION_KEY = 'test-master-key-32-characters!!';
describe.skip('DataSourceService', () => {
let service;
let testWorkspaceId;
let testUserId;
beforeEach(async () => {
await setupTestDatabase();
service = new DataSourceService();
// Create test workspace and user
const { dbRun, getDb } = await import('../../database/connection.js');
const db = getDb();
// Run migration to create data_sources table
const { runMigration } = await import('../../database/migrations/001-multi-source-tables.js');
await runMigration(db);
// Create user with unique email (to avoid conflicts between tests)
const uniqueEmail = `test_${Date.now()}_${Math.random().toString(36).substring(7)}@example.com`;
const userResult = await dbRun(
'INSERT INTO users (email, password_hash, name, encryption_key_encrypted) VALUES (?, ?, ?, ?)',
[uniqueEmail, 'hash', 'Test User', 'encrypted_key']
);
testUserId = userResult.lastInsertRowid;
// Create workspace (need spaces table first)
await dbRun(`
CREATE TABLE IF NOT EXISTS spaces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner_id INTEGER NOT NULL,
name TEXT NOT NULL,
type TEXT DEFAULT 'business'
)
`);
const spaceResult = await dbRun(
'INSERT INTO spaces (owner_id, name, type) VALUES (?, ?, ?)',
[testUserId, 'Test Space', 'business']
);
testWorkspaceId = `ws_${spaceResult.lastInsertRowid}`;
});
afterEach(async () => {
await cleanupTestDatabase();
});
describe.skip('create', () => {
test('should create data source with valid parameters', async () => {
const params = {
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'Neometal Shop CRM',
description: 'External MySQL database',
type: 'ssh+mysql',
sshHost: '203.0.113.10',
sshPort: 22,
sshUsername: 'root',
sshKeyName: 'ssh_key_neometal',
dbHost: 'localhost',
dbPort: 3306,
dbName: 'shop_crm',
dbUsername: 'crm_user',
dbPasswordKey: 'db_password_neometal'
};
const dataSource = await service.create(params);
expect(dataSource).toBeDefined();
expect(dataSource.id).toMatch(/^ds_[a-zA-Z0-9]+$/);
expect(dataSource.name).toBe('Neometal Shop CRM');
expect(dataSource.type).toBe('ssh+mysql');
expect(dataSource.workspace_id).toBe(testWorkspaceId);
expect(dataSource.created_by).toBe(testUserId);
});
test('should throw error if name is missing', async () => {
const params = {
workspaceId: testWorkspaceId,
userId: testUserId,
type: 'ssh+mysql'
};
await expect(service.create(params)).rejects.toThrow('name is required');
});
test('should throw error if type is invalid', async () => {
const params = {
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'Test DB',
type: 'invalid_type'
};
await expect(service.create(params)).rejects.toThrow('Invalid type');
});
test('should throw error if SSH parameters are missing for ssh+ type', async () => {
const params = {
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'Test DB',
type: 'ssh+mysql',
dbHost: 'localhost'
// Missing SSH parameters
};
await expect(service.create(params)).rejects.toThrow('SSH parameters are required');
});
});
describe.skip('get', () => {
test('should retrieve data source by ID', async () => {
// Create a data source first
const created = await service.create({
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'Test DB',
type: 'direct+mysql',
dbHost: 'localhost',
dbPort: 3306,
dbName: 'testdb',
dbUsername: 'user',
dbPasswordKey: 'pwd_key'
});
const retrieved = await service.get(created.id);
expect(retrieved).toBeDefined();
expect(retrieved.id).toBe(created.id);
expect(retrieved.name).toBe('Test DB');
});
test('should throw error if data source not found', async () => {
await expect(service.get('ds_nonexistent')).rejects.toThrow('Data source not found');
});
});
describe.skip('list', () => {
test('should list all data sources for workspace', async () => {
// Create multiple data sources
await service.create({
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'DB 1',
type: 'direct+mysql',
dbHost: 'localhost',
dbPort: 3306,
dbName: 'db1',
dbUsername: 'user1',
dbPasswordKey: 'pwd1'
});
await service.create({
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'DB 2',
type: 'ssh+postgres',
sshHost: '1.2.3.4',
sshPort: 22,
sshUsername: 'root',
sshKeyName: 'key_2',
dbHost: 'localhost',
dbPort: 5432,
dbName: 'db2',
dbUsername: 'user2',
dbPasswordKey: 'pwd2'
});
const dataSources = await service.list(testWorkspaceId);
expect(dataSources.length).toBeGreaterThanOrEqual(2);
// Find our created data sources
const db1 = dataSources.find(ds => ds.name === 'DB 1');
const db2 = dataSources.find(ds => ds.name === 'DB 2');
expect(db1).toBeDefined();
expect(db2).toBeDefined();
expect(db1.type).toBe('direct+mysql');
expect(db2.type).toBe('ssh+postgres');
});
test('should return empty array if no data sources for non-existent workspace', async () => {
const dataSources = await service.list('ws_nonexistent_999');
expect(dataSources).toEqual([]);
});
});
describe.skip('update', () => {
test('should update data source', async () => {
const created = await service.create({
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'Original Name',
type: 'direct+mysql',
dbHost: 'localhost',
dbPort: 3306,
dbName: 'testdb',
dbUsername: 'user',
dbPasswordKey: 'pwd_key'
});
const updated = await service.update(created.id, {
name: 'Updated Name',
description: 'New description'
});
expect(updated.name).toBe('Updated Name');
expect(updated.description).toBe('New description');
expect(updated.type).toBe('direct+mysql'); // unchanged
});
test('should throw error if trying to update non-existent data source', async () => {
await expect(service.update('ds_nonexistent', { name: 'New Name' }))
.rejects.toThrow('Data source not found');
});
});
describe.skip('delete', () => {
test('should delete data source', async () => {
const created = await service.create({
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'To Delete',
type: 'direct+mysql',
dbHost: 'localhost',
dbPort: 3306,
dbName: 'testdb',
dbUsername: 'user',
dbPasswordKey: 'pwd_key'
});
await service.delete(created.id);
await expect(service.get(created.id)).rejects.toThrow('Data source not found');
});
test('should throw error if deleting non-existent data source', async () => {
await expect(service.delete('ds_nonexistent')).rejects.toThrow('Data source not found');
});
});
describe.skip('testConnection', () => {
test('should test connection and update status', async () => {
const created = await service.create({
workspaceId: testWorkspaceId,
userId: testUserId,
name: 'Test DB',
type: 'direct+mysql',
dbHost: 'localhost',
dbPort: 3306,
dbName: 'testdb',
dbUsername: 'user',
dbPasswordKey: 'pwd_key'
});
// Mock test - in real implementation this would actually connect
const result = await service.testConnection(created.id);
expect(result).toBeDefined();
expect(result.status).toMatch(/^(success|failed)$/);
expect(result.tested_at).toBeDefined();
if (result.status === 'failed') {
expect(result.error).toBeDefined();
}
});
test('should throw error if testing non-existent data source', async () => {
await expect(service.testConnection('ds_nonexistent'))
.rejects.toThrow('Data source not found');
});
});
describe.skip('validation', () => {
test('should validate SSH+MySQL configuration', () => {
const config = {
type: 'ssh+mysql',
sshHost: '1.2.3.4',
sshPort: 22,
sshUsername: 'root',
sshKeyName: 'key_1',
dbHost: 'localhost',
dbPort: 3306,
dbName: 'testdb',
dbUsername: 'user',
dbPasswordKey: 'pwd_key'
};
expect(service.validateConfig(config)).toBe(true);
});
test('should validate Direct PostgreSQL configuration', () => {
const config = {
type: 'direct+postgres',
dbHost: '1.2.3.4',
dbPort: 5432,
dbName: 'testdb',
dbUsername: 'user',
dbPasswordKey: 'pwd_key'
};
expect(service.validateConfig(config)).toBe(true);
});
test('should fail validation for incomplete SSH config', () => {
const config = {
type: 'ssh+mysql',
sshHost: '1.2.3.4',
// Missing sshPort, sshUsername, sshKeyName
dbHost: 'localhost'
};
expect(() => service.validateConfig(config)).toThrow();
});
test('should fail validation for incomplete DB config', () => {
const config = {
type: 'direct+mysql',
dbHost: 'localhost',
// Missing dbPort, dbName, dbUsername, dbPasswordKey
};
expect(() => service.validateConfig(config)).toThrow();
});
});
});