Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
</p>

<p align="center">
<img src="https://img.shields.io/badge/version-0.1.0--beta.1-blue" alt="Version" />
<img src="https://img.shields.io/badge/version-0.1.0--beta.4-blue" alt="Version" />
<img src="https://img.shields.io/badge/license-MIT-green" alt="License" />
<img src="https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey" alt="Platform" />
<img src="https://img.shields.io/badge/databases-PostgreSQL%20%7C%20MySQL-orange" alt="Databases" />
<img src="https://img.shields.io/badge/databases-PostgreSQL%20%7C%20MySQL%20%7C%20MariaDB-orange" alt="Databases" />
</p>

<p align="center">
<a href="https://github.com/Yashh56/RelWave/releases"><strong>Download</strong></a> |
<a href="#-features"><strong>Features</strong></a> |
<a href="#-installation"><strong>Installation</strong></a> |
<a href="#-contributing"><strong>Contributing</strong></a>
<a href="#Features"><strong>Features</strong></a> |
<a href="#Installation"><strong>Installation</strong></a> |
<a href="#Contributing"><strong>Contributing</strong></a>
</p>

---
Expand Down Expand Up @@ -232,11 +232,71 @@ Database connection configurations are stored in:

### Running Tests

The bridge includes a comprehensive test suite with integration tests for database connectors.

#### Prerequisites

1. **Docker** - Required for running test databases:

```bash
cd bridge
docker-compose -f docker-compose.test.yml up -d
```

This starts PostgreSQL, MySQL, and MariaDB containers for testing.

2. **Environment Variables** - Create a `.env` file in the `bridge` directory:

```env
# PostgreSQL Test Configuration
REAL_POSTGRES_HOST=localhost
REAL_POSTGRES_PORT=5432
REAL_POSTGRES_USER=testuser
REAL_POSTGRES_PASSWORD=testpass
REAL_POSTGRES_DATABASE=testdb
REAL_POSTGRES_SSL=false

# MySQL Test Configuration
REAL_MYSQL_HOST=localhost
REAL_MYSQL_PORT=3306
REAL_MYSQL_USER=testuser
REAL_MYSQL_PASSWORD=testpass
REAL_MYSQL_DATABASE=testdb

# MariaDB Test Configuration
REAL_MARIADB_HOST=localhost
REAL_MARIADB_PORT=3307
REAL_MARIADB_USER=testuser
REAL_MARIADB_PASSWORD=testpass
REAL_MARIADB_DATABASE=testdb
```

#### Running Tests

```bash
cd bridge
pnpm test
```

#### Test Suites

| Test Suite | Description |
|------------|-------------|
| `databaseService.test.ts` | Database service CRUD operations and validation |
| `dbStore.test.ts` | Database store caching, encryption, and persistence |
| `connectionBuilder.test.ts` | Connection configuration building |
| `postgres.test.ts` | PostgreSQL connector integration tests |
| `mysql.test.ts` | MySQL connector integration tests |
| `mariadb.test.ts` | MariaDB connector integration tests |
| `*.cache.test.ts` | Query result caching tests for each connector |

#### Stopping Test Databases

```bash
cd bridge
docker-compose -f docker-compose.test.yml down
```

### Architecture

The application uses a **bridge architecture**:
Expand Down
84 changes: 63 additions & 21 deletions bridge/__tests__/databaseService.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, test } from "@jest/globals";
import { afterAll, describe, expect, test } from "@jest/globals";
import { DatabaseService } from "../src/services/databaseService";

const mockInput = {
Expand All @@ -11,32 +11,50 @@ const mockInput = {
ssl: true,
};

let createdDbId: string | null = null;
// Test database names that should be cleaned up
const TEST_DB_NAMES = ["TestDB", "DeleteTestDB"];

// Track all created database IDs for cleanup
const createdDbIds: string[] = [];

describe("Database Service Method", () => {
const dbService = new DatabaseService();

afterEach(async () => {
if (createdDbId) {
// Clean up ALL test databases after all tests complete
afterAll(async () => {
// First, clean up databases we explicitly tracked
for (const id of createdDbIds) {
try {
await dbService.deleteDatabase(createdDbId);
await dbService.deleteDatabase(id);
} catch (e) {
// Silently ignore "Database not found" errors - expected when test didn't create a DB
if (!(e instanceof Error && e.message === "Database not found")) {
console.warn("Cleanup failed:", e);
// Ignore - may already be deleted
}
}

// Then, clean up any remaining test databases by name (safety net)
try {
const dbs = await dbService.listDatabases();
for (const db of dbs) {
if (TEST_DB_NAMES.includes(db.name)) {
try {
await dbService.deleteDatabase(db.id);
} catch (e) {
// Ignore deletion errors during cleanup
}
}
}
createdDbId = null;
} catch (e) {
// Ignore errors during final cleanup
}
});

// Test Case 1: All required fields provided
test("should add database when all required fields are provided", async () => {
// Arrange
const dbService = new DatabaseService();
const payload = { ...mockInput };
// Act
const result = await dbService.addDatabase(payload);
createdDbId = result.id;
createdDbIds.push(result.id); // Track for cleanup
// Assert
expect(result).toBeDefined();
expect(result.name).toBe(payload.name);
Expand All @@ -46,7 +64,6 @@ describe("Database Service Method", () => {

test("should throw error when required field 'host' is missing", async () => {
// Arrange
const dbService = new DatabaseService();
const { host, ...payload } = mockInput;
// Act & Assert
await expect(dbService.addDatabase(payload)).rejects.toThrow(
Expand All @@ -57,7 +74,6 @@ describe("Database Service Method", () => {
// Test Case 3: Missing required field 'user'
test("should throw error when required field 'user' is missing", async () => {
// Arrange
const dbService = new DatabaseService();
const { user, ...payload } = mockInput;
// Act & Assert
await expect(dbService.addDatabase(payload)).rejects.toThrow(
Expand All @@ -68,7 +84,6 @@ describe("Database Service Method", () => {
// Test Case 4: Missing required field 'database'
test("should throw error when required field 'database' is missing", async () => {
// Arrange
const dbService = new DatabaseService();
const { database, ...payload } = mockInput;
// Act & Assert
await expect(dbService.addDatabase(payload)).rejects.toThrow(
Expand All @@ -79,7 +94,6 @@ describe("Database Service Method", () => {
// Test Case 5: Missing required field 'type'
test("should throw error when required field 'type' is missing", async () => {
// Arrange
const dbService = new DatabaseService();
const { type, ...payload } = mockInput;
// Act & Assert
await expect(dbService.addDatabase(payload)).rejects.toThrow(
Expand All @@ -90,7 +104,6 @@ describe("Database Service Method", () => {
// Test Case 6: Missing required field 'name'
test("should throw error when required field 'name' is missing", async () => {
// Arrange
const dbService = new DatabaseService();
const { name, ...payload } = mockInput;
// Act & Assert
await expect(dbService.addDatabase(payload)).rejects.toThrow(
Expand All @@ -101,7 +114,6 @@ describe("Database Service Method", () => {
// Test Case 7: Missing required field 'port'
test("should throw error when required field 'port' is missing", async () => {
// Arrange
const dbService = new DatabaseService();
const { port, ...payload } = mockInput;
// Act & Assert
await expect(dbService.addDatabase(payload)).rejects.toThrow(
Expand All @@ -112,7 +124,6 @@ describe("Database Service Method", () => {
// Test Case 8 : List databases does not expose credentialId
test("should not expose credentialId when listing databases", async () => {
// Arrange
const dbService = new DatabaseService();
// Act
const dbs = await dbService.listDatabases();
// Assert
Expand All @@ -124,7 +135,6 @@ describe("Database Service Method", () => {
// Test Case 9: Get database connection for non-existent DB
test("should throw error when getting connection for non-existent database", async () => {
// Arrange
const dbService = new DatabaseService();
const fakeDbId = "nonexistent-id";
// Act & Assert
await expect(dbService.getDatabaseConnection(fakeDbId)).rejects.toThrow(
Expand All @@ -135,7 +145,6 @@ describe("Database Service Method", () => {
// Test Case 10: Update database with missing ID
test("should throw error when updating database with missing ID", async () => {
// Arrange
const dbService = new DatabaseService();
const payload = { name: "UpdatedName" };
// Act & Assert
await expect(dbService.updateDatabase("", payload)).rejects.toThrow(
Expand All @@ -146,8 +155,41 @@ describe("Database Service Method", () => {
// Test Case 11: Delete database with missing ID
test("should throw error when deleting database with missing ID", async () => {
// Arrange
const dbService = new DatabaseService();
// Act & Assert
await expect(dbService.deleteDatabase("")).rejects.toThrow("Missing id");
});

// Test Case 12: Successfully delete an existing database
test("should successfully delete an existing database", async () => {
// Arrange
const payload = { ...mockInput, name: "DeleteTestDB" };

// Act - Create a database first
const createdDb = await dbService.addDatabase(payload);
expect(createdDb).toBeDefined();
expect(createdDb.id).toBeDefined();

// Verify it exists in the list
let dbList = await dbService.listDatabases();
expect(dbList.some((db) => db.id === createdDb.id)).toBe(true);

// Act - Delete the database
await dbService.deleteDatabase(createdDb.id);

// Assert - Verify it no longer exists in the list
dbList = await dbService.listDatabases();
expect(dbList.some((db) => db.id === createdDb.id)).toBe(false);

// Note: No need to set createdDbId here since we already deleted it
});

// Test Case 13: Delete non-existent database should throw error
test("should throw error when deleting non-existent database", async () => {
// Arrange
const fakeDbId = "nonexistent-database-id";
// Act & Assert
await expect(dbService.deleteDatabase(fakeDbId)).rejects.toThrow(
"Database not found"
);
});
});
6 changes: 3 additions & 3 deletions bridge/__tests__/dbStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const TEST_CREDENTIALS_FILE = path.join(TEST_CONFIG_FOLDER, ".credentials");
const TEST_ENCRYPTION_KEY = "test-encryption-key";

// Short TTL for testing cache expiration
const SHORT_CACHE_TTL = 100; // 100ms for testing
const SHORT_CACHE_TTL = 200; // 200ms for testing
const NORMAL_CACHE_TTL = 30000; // 30 seconds

const mockDBPayload = {
Expand Down Expand Up @@ -207,8 +207,8 @@ describe("DbStore Cache Tests", () => {
// Cache should be populated after addDB (saveAll updates cache)
expect(shortTtlStore.getCacheStats().configCached).toBe(true);

// Wait for TTL to expire
await new Promise((resolve) => setTimeout(resolve, SHORT_CACHE_TTL + 50));
// Wait for TTL to expire (add extra buffer for system lag)
await new Promise((resolve) => setTimeout(resolve, SHORT_CACHE_TTL + 150));

// Cache should be expired now
expect(shortTtlStore.getCacheStats().configCached).toBe(false);
Expand Down