* adds complete tests for database

* adds healtCheck and connection status related methods DatabaseConnection class
This commit is contained in:
albert
2025-07-28 16:30:28 +02:00
parent 53e98dfe52
commit 3981581c45
2 changed files with 306 additions and 52 deletions

View File

@ -1,6 +1,220 @@
// test temporal para probar que jest funciona
import mongoose from 'mongoose';
import { DatabaseConnection } from '../config/database.js';
import { Logger } from '../utils/logger.js';
// Mock mongoose
jest.mock('mongoose', () => {
const mockConnection = {
readyState: 0,
on: jest.fn(),
once: jest.fn()
};
return {
connect: jest.fn(),
disconnect: jest.fn(),
connection: mockConnection
};
});
// Mock Logger
jest.mock('../utils/logger.js', () => ({
Logger: {
database: {
connected: jest.fn(),
disconnected: jest.fn(),
reconnected: jest.fn(),
alreadyConnected: jest.fn(),
notConnected: jest.fn()
},
error: jest.fn()
}
}));
// Mock config
jest.mock('../config/config.js', () => ({
config: {
mongodbUri: 'mongodb://localhost:27017/test'
}
}));
describe('DatabaseConnection', () => {
describe('Singleton Pattern', () => {
test('should return the same instance', () => {})
})
})
let mockMongoose: jest.Mocked<typeof mongoose>;
let mockLogger: jest.Mocked<typeof Logger>;
let databaseConnection: DatabaseConnection;
beforeEach(() => {
jest.clearAllMocks();
mockMongoose = mongoose as jest.Mocked<typeof mongoose>;
mockLogger = Logger as jest.Mocked<typeof Logger>;
// Reset singleton instance
(DatabaseConnection as any).instance = null;
databaseConnection = DatabaseConnection.getInstance();
});
describe('Singleton Pattern', () => {
test('should return the same instance', () => {
const instance1 = DatabaseConnection.getInstance();
const instance2 = DatabaseConnection.getInstance();
expect(instance1).toBe(instance2);
});
});
describe('connect', () => {
test('should connect to MongoDB successfully', async () => {
mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
await databaseConnection.connect();
expect(mockMongoose.connect).toHaveBeenCalledWith('mongodb://localhost:27017/test', {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
bufferCommands: false
});
expect(mockLogger.database.connected).toHaveBeenCalled();
});
test('should not connect if already connected', async () => {
// Set as already connected
(databaseConnection as any).isConnected = true;
await databaseConnection.connect();
expect(mockMongoose.connect).not.toHaveBeenCalled();
expect(mockLogger.database.alreadyConnected).toHaveBeenCalled();
});
test('should handle connection errors', async () => {
const error = new Error('Connection failed');
mockMongoose.connect.mockRejectedValueOnce(error);
await expect(databaseConnection.connect()).rejects.toThrow('Connection failed');
expect(mockLogger.error).toHaveBeenCalledWith('Failed to connect to MongoDB', { error });
});
test('should set up connection event listeners', async () => {
mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
await databaseConnection.connect();
expect(mockMongoose.connection.on).toHaveBeenCalledWith('error', expect.any(Function));
expect(mockMongoose.connection.on).toHaveBeenCalledWith('disconnected', expect.any(Function));
expect(mockMongoose.connection.on).toHaveBeenCalledWith('reconnected', expect.any(Function));
});
});
describe('disconnect', () => {
test('should disconnect from MongoDB successfully', async () => {
// Set as connected
(databaseConnection as any).isConnected = true;
mockMongoose.disconnect.mockResolvedValueOnce(undefined);
await databaseConnection.disconnect();
expect(mockMongoose.disconnect).toHaveBeenCalled();
expect(mockLogger.database.disconnected).toHaveBeenCalled();
expect((databaseConnection as any).isConnected).toBe(false);
});
test('should not disconnect if not connected', async () => {
// Set as not connected
(databaseConnection as any).isConnected = false;
await databaseConnection.disconnect();
expect(mockMongoose.disconnect).not.toHaveBeenCalled();
expect(mockLogger.database.notConnected).toHaveBeenCalled();
});
test('should handle disconnection errors', async () => {
const error = new Error('Disconnection failed');
(databaseConnection as any).isConnected = true;
mockMongoose.disconnect.mockRejectedValueOnce(error);
await expect(databaseConnection.disconnect()).rejects.toThrow('Disconnection failed');
expect(mockLogger.error).toHaveBeenCalledWith('Error disconnecting from MongoDB', { error });
});
});
describe('isConnected', () => {
test('should return true when connected', () => {
(databaseConnection as any).isConnected = true;
expect(databaseConnection.getConnectionStatus()).toBe(true);
});
test('should return false when not connected', () => {
(databaseConnection as any).isConnected = false;
expect(databaseConnection.getConnectionStatus()).toBe(false);
});
});
describe('Event Handlers', () => {
test('should handle connection error event', async () => {
mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
let errorHandler: Function;
(mockMongoose.connection.on as jest.Mock).mockImplementation((event, handler) => {
if (event === 'error') {
errorHandler = handler;
}
return mockMongoose.connection;
});
await databaseConnection.connect();
// Simulate error event
const testError = new Error('Connection error');
errorHandler!(testError);
expect(mockLogger.error).toHaveBeenCalledWith('MongoDB connection error', { error: testError });
expect((databaseConnection as any).isConnected).toBe(false);
});
test('should handle disconnected event', async () => {
mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
let disconnectedHandler: Function;
(mockMongoose.connection.on as jest.Mock).mockImplementation((event, handler) => {
if (event === 'disconnected') {
disconnectedHandler = handler;
}
return mockMongoose.connection;
});
await databaseConnection.connect();
// Simulate disconnected event
disconnectedHandler!();
expect(mockLogger.database.disconnected).toHaveBeenCalled();
expect((databaseConnection as any).isConnected).toBe(false);
});
test('should handle reconnected event', async () => {
mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
let reconnectedHandler: Function;
(mockMongoose.connection.on as jest.Mock).mockImplementation((event, handler) => {
if (event === 'reconnected') {
reconnectedHandler = handler;
}
return mockMongoose.connection;
});
await databaseConnection.connect();
// Simulate reconnected event
reconnectedHandler!();
expect(mockLogger.database.reconnected).toHaveBeenCalled();
expect((databaseConnection as any).isConnected).toBe(true);
});
});
});

View File

@ -1,55 +1,95 @@
import mongoose from 'mongoose';
import config from './config'
import { config } from '../config/config.js';
import { Logger } from '../utils/logger.js';
export class DatabaseConnection {
private static instance: DatabaseConnection;
private isConnected: boolean = false; // a implementar
private static instance: DatabaseConnection;
private isConnected: boolean = false;
private constructor() {}
private constructor() {}
public static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
public static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
public async connect(): Promise<void> {
if (this.isConnected) {
Logger.database.alreadyConnected();
return;
}
public async connect(): Promise<void> {
if (this.isConnected) {
console.log("database is already connected")
return;
}
try {
await mongoose.connect(config.mongodbUri, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
bufferCommands: false,
});
try {
await mongoose.connect(config.mongodbUri, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
bufferCommands: false,
});
this.isConnected = true;
Logger.database.connected();
this.isConnected = true;
console.log("database connected")
mongoose.connection.on('error', (error) => {
console.log('MongoDB connection error', { error });
this.isConnected = false;
});
mongoose.connection.on('disconnected', () => {
console.log('MongoDB connection disconnected');
this.isConnected = false;
});
mongoose.connection.on('reconnected', () => {
console.log('MongoDB connection reconnected');
this.isConnected = true;
});
} catch (error) {
console.log('MongoDB Failed to connect', { error });
mongoose.connection.on('error', (error) => {
Logger.error('MongoDB connection error', { error });
this.isConnected = false;
throw error;
}
});
mongoose.connection.on('disconnected', () => {
Logger.database.disconnected();
this.isConnected = false;
});
mongoose.connection.on('reconnected', () => {
Logger.database.reconnected();
this.isConnected = true;
});
} catch (error) {
Logger.error('Failed to connect to MongoDB', { error });
this.isConnected = false;
throw error;
}
}
public async disconnect(): Promise<void> {
if (!this.isConnected) {
Logger.database.notConnected();
return;
}
try {
await mongoose.disconnect();
this.isConnected = false;
Logger.database.disconnected();
} catch (error) {
Logger.error('Error disconnecting from MongoDB', { error });
throw error;
}
}
public getConnectionStatus(): boolean {
return this.isConnected && mongoose.connection.readyState === 1;
}
public async healthCheck(): Promise<{ status: string; message: string }> {
try {
if (!this.isConnected) {
return { status: 'error', message: 'Database not connected' };
}
await mongoose.connection.db?.admin().ping();
return { status: 'ok', message: 'Database connection is healthy' };
} catch (error) {
return {
status: 'error',
message: `Database health check failed: ${error instanceof Error ? error.message : 'Unknown error'}`
};
}
}
}
export const database = DatabaseConnection.getInstance();
export default database;