Skip to main content
Glama
CloudflareD1Repository.test.ts20.9 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { CloudflareD1Repository } from './CloudflareD1Repository'; import { CloudflareAPIClient } from '../http/CloudflareAPIClient'; import { DatabaseConfig } from '../config/DatabaseConfig'; import { Environment } from '../../domain/value-objects/Environment'; describe('CloudflareD1Repository', () => { let repository: CloudflareD1Repository; let mockApiClient: CloudflareAPIClient; let mockDatabaseConfig: DatabaseConfig; beforeEach(() => { // Create mock API client mockApiClient = { query: vi.fn(), getDatabaseInfo: vi.fn(), } as unknown as CloudflareAPIClient; // Create mock database config const databases = new Map(); databases.set(Environment.DEVELOPMENT, { name: 'dev_db', id: 'dev-123' }); databases.set(Environment.PRODUCTION, { name: 'prod_db', id: 'prod-456' }); mockDatabaseConfig = new DatabaseConfig(databases); repository = new CloudflareD1Repository(mockApiClient, mockDatabaseConfig, Environment.DEVELOPMENT); vi.clearAllMocks(); }); describe('constructor', () => { it('should create repository with dependencies', () => { expect(repository).toBeInstanceOf(CloudflareD1Repository); }); it('should be frozen for immutability', () => { expect(Object.isFrozen(repository)).toBe(true); }); it('should default to DEVELOPMENT environment', () => { const repo = new CloudflareD1Repository(mockApiClient, mockDatabaseConfig); expect(repo).toBeInstanceOf(CloudflareD1Repository); }); }); describe('fetchDatabaseSchema()', () => { it('should fetch complete database schema', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)' }, { type: 'table', name: 'posts', sql: 'CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER)' }, ], }); // Mock PRAGMA table_info for users (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }, { cid: 1, name: 'name', type: 'TEXT', notnull: 0, dflt_value: null, pk: 0 }, ], }); // Mock PRAGMA index_list for users (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list for users (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA table_info for posts (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }, { cid: 1, name: 'user_id', type: 'INTEGER', notnull: 0, dflt_value: null, pk: 0 }, ], }); // Mock PRAGMA index_list for posts (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list for posts (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { id: 0, seq: 0, table: 'users', from: 'user_id', to: 'id', on_update: 'NO ACTION', on_delete: 'CASCADE', match: 'NONE', }, ], }); const schema = await repository.fetchDatabaseSchema(); expect(schema.name).toBe('dev_db'); expect(schema.environment).toBe(Environment.DEVELOPMENT); expect(schema.tables).toHaveLength(2); expect(schema.tables[0].name).toBe('users'); expect(schema.tables[1].name).toBe('posts'); expect(schema.fetchedAt).toBeInstanceOf(Date); }); it('should use provided database ID', async () => { // Mock sqlite_master query with at least one table (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); await repository.fetchDatabaseSchema('custom-db-id'); expect(mockApiClient.query).toHaveBeenCalledWith( 'custom-db-id', expect.stringContaining('SELECT type, name, sql FROM sqlite_master'), ); }); it('should use environment database ID when not provided', async () => { // Mock sqlite_master query with at least one table (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); await repository.fetchDatabaseSchema(); expect(mockApiClient.query).toHaveBeenCalledWith( 'dev-123', expect.stringContaining('SELECT type, name, sql FROM sqlite_master'), ); }); it('should return empty tables array when no tables exist', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(tables).toHaveLength(0); }); }); describe('fetchTableDetails()', () => { it('should fetch all tables when no table name specified', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(mockApiClient.query).toHaveBeenCalledWith( 'dev-123', expect.stringContaining("WHERE type IN ('table', 'view') ORDER BY name"), ); expect(tables).toHaveLength(1); }); it('should fetch specific table when table name specified', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); const tables = await repository.fetchTableDetails(undefined, 'users'); expect(mockApiClient.query).toHaveBeenCalledWith( 'dev-123', expect.stringContaining("AND name = 'users'"), ); }); it('should include table columns', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER, name TEXT)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }, { cid: 1, name: 'name', type: 'TEXT', notnull: 0, dflt_value: "'unknown'", pk: 0 }, ], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(tables[0].columns).toHaveLength(2); expect(tables[0].columns[0].name).toBe('id'); expect(tables[0].columns[0].isPrimaryKey).toBe(true); expect(tables[0].columns[1].name).toBe('name'); expect(tables[0].columns[1].isNullable).toBe(true); expect(tables[0].columns[1].defaultValue).toBe("'unknown'"); }); it('should include table indexes', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER, email TEXT)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }, { cid: 1, name: 'email', type: 'TEXT', notnull: 1, dflt_value: null, pk: 0 }, ], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ seq: 0, name: 'idx_users_email', unique: 1, origin: 'c', partial: 0 }], }); // Mock PRAGMA index_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ seqno: 0, cid: 1, name: 'email' }], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(tables[0].indexes).toHaveLength(1); expect(tables[0].indexes[0].name).toBe('idx_users_email'); expect(tables[0].indexes[0].columns).toEqual(['email']); expect(tables[0].indexes[0].isUnique).toBe(true); }); it('should include table foreign keys', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'posts', sql: 'CREATE TABLE posts (id INTEGER, user_id INTEGER)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }, { cid: 1, name: 'user_id', type: 'INTEGER', notnull: 0, dflt_value: null, pk: 0 }, ], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { id: 0, seq: 0, table: 'users', from: 'user_id', to: 'id', on_update: 'NO ACTION', on_delete: 'CASCADE', match: 'NONE', }, ], }); const tables = await repository.fetchTableDetails(); expect(tables[0].foreignKeys).toHaveLength(1); expect(tables[0].foreignKeys[0].table).toBe('posts'); expect(tables[0].foreignKeys[0].column).toBe('user_id'); expect(tables[0].foreignKeys[0].referencesTable).toBe('users'); expect(tables[0].foreignKeys[0].referencesColumn).toBe('id'); expect(tables[0].foreignKeys[0].onDelete).toBe('CASCADE'); }); it('should handle views', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { type: 'view', name: 'active_users', sql: 'CREATE VIEW active_users AS SELECT * FROM users' }, ], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'id', type: 'INTEGER', notnull: 0, dflt_value: null, pk: 0 }], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(tables[0].type).toBe('view'); expect(tables[0].isView()).toBe(true); }); it('should return empty array when no tables', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(tables).toHaveLength(0); }); }); describe('fetchIndexInformation()', () => { it('should fetch all indexes from sqlite_master', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [ { name: 'idx_users_email', tbl_name: 'users', sql: 'CREATE UNIQUE INDEX idx_users_email ON users(email)', }, { name: 'idx_posts_user_id', tbl_name: 'posts', sql: 'CREATE INDEX idx_posts_user_id ON posts(user_id)', }, ], }); const indexes = await repository.fetchIndexInformation(); expect(indexes).toHaveLength(2); expect(indexes[0].name).toBe('idx_users_email'); expect(indexes[0].tableName).toBe('users'); expect(indexes[0].columns).toEqual(['email']); expect(indexes[0].isUnique).toBe(true); expect(indexes[1].name).toBe('idx_posts_user_id'); expect(indexes[1].tableName).toBe('posts'); expect(indexes[1].columns).toEqual(['user_id']); expect(indexes[1].isUnique).toBe(false); }); it('should parse multi-column indexes', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [ { name: 'idx_users_name_email', tbl_name: 'users', sql: 'CREATE INDEX idx_users_name_email ON users(name, email)', }, ], }); const indexes = await repository.fetchIndexInformation(); expect(indexes[0].columns).toEqual(['name', 'email']); }); it('should use provided database ID', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [], }); await repository.fetchIndexInformation('custom-db-id'); expect(mockApiClient.query).toHaveBeenCalledWith( 'custom-db-id', expect.stringContaining("WHERE type = 'index'"), ); }); it('should return empty array when no indexes', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [], }); const indexes = await repository.fetchIndexInformation(); expect(indexes).toHaveLength(0); }); }); describe('executeSQLQuery()', () => { it('should execute SQL query and return results', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ], meta: { duration: 15, rows_read: 2, }, }); const result = await repository.executeSQLQuery('db-123', 'SELECT * FROM users'); expect(result.success).toBe(true); expect(result.results).toHaveLength(2); expect(result.meta?.duration).toBe(15); expect(result.meta?.rows_read).toBe(2); }); it('should handle empty results', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: true, results: [], }); const result = await repository.executeSQLQuery('db-123', 'DELETE FROM users WHERE id = 999'); expect(result.success).toBe(true); expect(result.results).toHaveLength(0); }); it('should handle failed queries', async () => { (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValue({ success: false, results: [], }); const result = await repository.executeSQLQuery('db-123', 'INVALID SQL'); expect(result.success).toBe(false); }); }); describe('private methods behavior', () => { it('should handle columns with nullable=true (notnull=0)', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (name TEXT)' }], }); // Mock PRAGMA table_info with notnull=0 (nullable) (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'name', type: 'TEXT', notnull: 0, dflt_value: null, pk: 0 }], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(tables[0].columns[0].isNullable).toBe(true); }); it('should handle indexes with origin=pk (primary key)', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'users', sql: 'CREATE TABLE users (id INTEGER PRIMARY KEY)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'id', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 1 }], }); // Mock PRAGMA index_list with origin=pk (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ seq: 0, name: 'sqlite_autoindex_users_1', unique: 1, origin: 'pk', partial: 0 }], }); // Mock PRAGMA index_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ seqno: 0, cid: 0, name: 'id' }], }); // Mock PRAGMA foreign_key_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); const tables = await repository.fetchTableDetails(); expect(tables[0].indexes[0].isPrimaryKey).toBe(true); }); it('should handle foreign keys with null on_delete/on_update', async () => { // Mock sqlite_master query (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ type: 'table', name: 'posts', sql: 'CREATE TABLE posts (user_id INTEGER)' }], }); // Mock PRAGMA table_info (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [{ cid: 0, name: 'user_id', type: 'INTEGER', notnull: 0, dflt_value: null, pk: 0 }], }); // Mock PRAGMA index_list (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [], }); // Mock PRAGMA foreign_key_list with null on_delete/on_update (mockApiClient.query as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ success: true, results: [ { id: 0, seq: 0, table: 'users', from: 'user_id', to: 'id', on_update: null, on_delete: null, match: 'NONE', }, ], }); const tables = await repository.fetchTableDetails(); expect(tables[0].foreignKeys[0].onDelete).toBe('NO ACTION'); expect(tables[0].foreignKeys[0].onUpdate).toBe('NO ACTION'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/semanticintent/semantic-d1-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server