Skip to main content
Glama
ValidateSchemaUseCase.test.ts10.8 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { ValidateSchemaUseCase, ValidationSeverity } from './ValidateSchemaUseCase'; import { ICloudflareD1Repository } from '../../domain/repositories/ICloudflareD1Repository'; import { ICacheProvider } from '../ports/ICacheProvider'; import { SchemaAnalyzer } from '../../domain/services/SchemaAnalyzer'; import { DatabaseConfig } from '../../infrastructure/config/DatabaseConfig'; import { Environment } from '../../domain/value-objects/Environment'; import { DatabaseSchema } from '../../domain/entities/DatabaseSchema'; import { TableInfo } from '../../domain/entities/TableInfo'; import { Column } from '../../domain/entities/Column'; import { ForeignKey } from '../../domain/entities/ForeignKey'; describe('ValidateSchemaUseCase', () => { let useCase: ValidateSchemaUseCase; let mockRepository: ICloudflareD1Repository; let mockCache: ICacheProvider; let schemaAnalyzer: SchemaAnalyzer; let mockDatabaseConfig: DatabaseConfig; beforeEach(() => { // Mock repository mockRepository = { fetchDatabaseSchema: vi.fn(), fetchTableDetails: vi.fn(), fetchIndexInformation: vi.fn(), executeSQLQuery: vi.fn(), } as unknown as ICloudflareD1Repository; // Mock cache mockCache = { get: vi.fn(), set: vi.fn(), delete: vi.fn(), clear: vi.fn(), has: vi.fn(), } as unknown as ICacheProvider; // Real schema analyzer schemaAnalyzer = new SchemaAnalyzer(); // 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-789' }); mockDatabaseConfig = new DatabaseConfig(databases); useCase = new ValidateSchemaUseCase(mockRepository, schemaAnalyzer, mockDatabaseConfig, mockCache); vi.clearAllMocks(); }); describe('constructor', () => { it('should create use case and freeze instance', () => { expect(Object.isFrozen(useCase)).toBe(true); }); }); describe('execute()', () => { it('should validate schema with no issues', async () => { (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined); // Create valid schema const columns = [new Column('id', 'INTEGER', true, false, null)]; const table = new TableInfo('users', 'table', columns, [], []); const schema = new DatabaseSchema('dev_db', Environment.DEVELOPMENT, [table], new Date()); (mockRepository.fetchDatabaseSchema as ReturnType<typeof vi.fn>).mockResolvedValue(schema); const result = await useCase.execute({ environment: Environment.DEVELOPMENT, }); expect(result.isValid).toBe(true); expect(result.errorCount).toBe(0); expect(result.warningCount).toBe(0); expect(result.infoCount).toBe(1); // No indexes info expect(result.issues).toHaveLength(1); expect(result.issues[0].category).toBe('No Indexes'); }); it('should detect table without primary key', async () => { (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined); // Table with no primary key const columns = [new Column('name', 'TEXT', false, false, null)]; const table = new TableInfo('users', 'table', columns, [], []); const schema = new DatabaseSchema('dev_db', Environment.DEVELOPMENT, [table], new Date()); (mockRepository.fetchDatabaseSchema as ReturnType<typeof vi.fn>).mockResolvedValue(schema); const result = await useCase.execute({ environment: Environment.DEVELOPMENT, }); expect(result.isValid).toBe(true); // No errors, just warnings expect(result.warningCount).toBe(1); const pkIssue = result.issues.find((i) => i.category === 'Missing Primary Key'); expect(pkIssue).toBeDefined(); expect(pkIssue?.severity).toBe(ValidationSeverity.WARNING); expect(pkIssue?.table).toBe('users'); }); it('should detect orphaned foreign key', async () => { (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined); // Foreign key references non-existent table const columns = [ new Column('id', 'INTEGER', true, false, null), new Column('user_id', 'INTEGER', false, false, null), ]; const foreignKeys = [new ForeignKey('orders', 'user_id', 'users', 'id', 'CASCADE', null)]; const table = new TableInfo('orders', 'table', columns, [], foreignKeys); const schema = new DatabaseSchema('dev_db', Environment.DEVELOPMENT, [table], new Date()); (mockRepository.fetchDatabaseSchema as ReturnType<typeof vi.fn>).mockResolvedValue(schema); const result = await useCase.execute({ environment: Environment.DEVELOPMENT, }); expect(result.isValid).toBe(false); // Has errors expect(result.errorCount).toBe(1); const fkIssue = result.issues.find((i) => i.category === 'Orphaned Foreign Key'); expect(fkIssue).toBeDefined(); expect(fkIssue?.severity).toBe(ValidationSeverity.ERROR); expect(fkIssue?.table).toBe('orders'); expect(fkIssue?.column).toBe('user_id'); }); it('should detect invalid foreign key column reference', async () => { (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined); // Foreign key references non-existent column const usersColumns = [new Column('id', 'INTEGER', true, false, null)]; const usersTable = new TableInfo('users', 'table', usersColumns, [], []); const ordersColumns = [ new Column('id', 'INTEGER', true, false, null), new Column('user_id', 'INTEGER', false, false, null), ]; const foreignKeys = [ new ForeignKey('orders', 'user_id', 'users', 'nonexistent_id', 'CASCADE', null), ]; const ordersTable = new TableInfo('orders', 'table', ordersColumns, [], foreignKeys); const schema = new DatabaseSchema( 'dev_db', Environment.DEVELOPMENT, [usersTable, ordersTable], new Date(), ); (mockRepository.fetchDatabaseSchema as ReturnType<typeof vi.fn>).mockResolvedValue(schema); const result = await useCase.execute({ environment: Environment.DEVELOPMENT, }); expect(result.isValid).toBe(false); expect(result.errorCount).toBe(1); const fkIssue = result.issues.find((i) => i.category === 'Invalid Foreign Key'); expect(fkIssue).toBeDefined(); expect(fkIssue?.severity).toBe(ValidationSeverity.ERROR); expect(fkIssue?.table).toBe('orders'); expect(fkIssue?.details?.referencedColumn).toBe('nonexistent_id'); }); it('should detect nullable foreign key without SET NULL', async () => { (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined); const usersColumns = [new Column('id', 'INTEGER', true, false, null)]; const usersTable = new TableInfo('users', 'table', usersColumns, [], []); const ordersColumns = [ new Column('id', 'INTEGER', true, false, null), new Column('user_id', 'INTEGER', false, true, null), // Nullable ]; const foreignKeys = [new ForeignKey('orders', 'user_id', 'users', 'id', 'CASCADE', null)]; const ordersTable = new TableInfo('orders', 'table', ordersColumns, [], foreignKeys); const schema = new DatabaseSchema( 'dev_db', Environment.DEVELOPMENT, [usersTable, ordersTable], new Date(), ); (mockRepository.fetchDatabaseSchema as ReturnType<typeof vi.fn>).mockResolvedValue(schema); const result = await useCase.execute({ environment: Environment.DEVELOPMENT, }); expect(result.isValid).toBe(true); // Warning, not error expect(result.warningCount).toBeGreaterThan(0); const fkIssue = result.issues.find((i) => i.category === 'Nullable Foreign Key'); expect(fkIssue).toBeDefined(); expect(fkIssue?.severity).toBe(ValidationSeverity.WARNING); }); it('should use cached schema when available', async () => { const columns = [new Column('id', 'INTEGER', true, false, null)]; const table = new TableInfo('products', 'table', columns, [], []); const cachedSchema = new DatabaseSchema('dev_db', Environment.DEVELOPMENT, [table], new Date()); (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(cachedSchema); await useCase.execute({ environment: Environment.DEVELOPMENT, }); // Verify cache was checked expect(mockCache.get).toHaveBeenCalledWith('schema:development'); // Verify repository was NOT called expect(mockRepository.fetchDatabaseSchema).not.toHaveBeenCalled(); // Verify cache was NOT set again expect(mockCache.set).not.toHaveBeenCalled(); }); it('should handle different environments correctly', async () => { (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined); const columns = [new Column('id', 'INTEGER', true, false, null)]; const table = new TableInfo('items', 'table', columns, [], []); const schema = new DatabaseSchema('prod_db', Environment.PRODUCTION, [table], new Date()); (mockRepository.fetchDatabaseSchema as ReturnType<typeof vi.fn>).mockResolvedValue(schema); const result = await useCase.execute({ environment: Environment.PRODUCTION, }); // Verify correct database ID used expect(mockRepository.fetchDatabaseSchema).toHaveBeenCalledWith('prod-789'); // Verify correct cache key expect(mockCache.set).toHaveBeenCalledWith('schema:production', schema, 600); expect(result.environment).toBe(Environment.PRODUCTION); expect(result.databaseName).toBe('prod_db'); }); it('should count issues by severity correctly', async () => { (mockCache.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined); // Create schema with multiple issues const usersColumns = [ new Column('id', 'INTEGER', true, false, null), new Column('name', 'TEXT', false, true, null), ]; const usersTable = new TableInfo('users', 'table', usersColumns, [], []); const ordersColumns = [ new Column('id', 'INTEGER', true, false, null), new Column('user_id', 'INTEGER', false, true, null), // Nullable FK ]; const foreignKeys = [ new ForeignKey('orders', 'user_id', 'nonexistent', 'id', 'CASCADE', null), // Orphaned FK ]; const ordersTable = new TableInfo('orders', 'table', ordersColumns, [], foreignKeys); const noPkColumns = [new Column('value', 'TEXT', false, false, null)]; const noPkTable = new TableInfo('settings', 'table', noPkColumns, [], []); // No primary key const schema = new DatabaseSchema( 'dev_db', Environment.DEVELOPMENT, [usersTable, ordersTable, noPkTable], new Date(), ); (mockRepository.fetchDatabaseSchema as ReturnType<typeof vi.fn>).mockResolvedValue(schema); const result = await useCase.execute({ environment: Environment.DEVELOPMENT, }); expect(result.isValid).toBe(false); // Has errors expect(result.errorCount).toBe(1); // Orphaned FK expect(result.warningCount).toBeGreaterThan(0); // No PK warnings expect(result.infoCount).toBeGreaterThan(0); // No indexes info }); }); });

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