Skip to main content
Glama
OptimizationService.test.ts10.4 kB
import { describe, it, expect, beforeEach } from 'vitest'; import { Column } from '../entities/Column'; import { ForeignKey } from '../entities/ForeignKey'; import { Index } from '../entities/Index'; import { Relationship } from '../entities/Relationship'; import { TableInfo } from '../entities/TableInfo'; import { Optimization } from '../entities/Optimization'; import { OptimizationService } from './OptimizationService'; describe('OptimizationService', () => { let service: OptimizationService; beforeEach(() => { service = new OptimizationService(); }); describe('checkMissingPrimaryKeys()', () => { it('should detect tables without primary keys', () => { const noPkTable = new TableInfo('logs', 'table', [ new Column('timestamp', 'TEXT'), new Column('message', 'TEXT'), ]); const optimizations = service.checkMissingPrimaryKeys([noPkTable]); expect(optimizations.length).toBe(1); expect(optimizations[0].type).toBe('missing_primary_key'); expect(optimizations[0].table).toBe('logs'); expect(optimizations[0].priority).toBe('high'); }); it('should not flag tables with primary keys', () => { const withPk = new TableInfo('users', 'table', [ new Column('id', 'INTEGER', true), new Column('name', 'TEXT'), ]); const optimizations = service.checkMissingPrimaryKeys([withPk]); expect(optimizations.length).toBe(0); }); it('should not flag views', () => { const view = new TableInfo('user_view', 'view', [ new Column('id', 'INTEGER'), new Column('name', 'TEXT'), ]); const optimizations = service.checkMissingPrimaryKeys([view]); expect(optimizations.length).toBe(0); }); }); describe('checkMissingIndexes()', () => { it('should detect foreign keys without indexes', () => { const postsTable = new TableInfo( 'posts', 'table', [ new Column('id', 'INTEGER', true), new Column('user_id', 'INTEGER'), new Column('title', 'TEXT'), ], [], // No indexes! [new ForeignKey('posts', 'user_id', 'users', 'id')] ); const relationships = [new Relationship('posts', 'user_id', 'users', 'id')]; const optimizations = service.checkMissingIndexes([postsTable], relationships); expect(optimizations.length).toBe(1); expect(optimizations[0].type).toBe('missing_index'); expect(optimizations[0].table).toBe('posts'); expect(optimizations[0].column).toBe('user_id'); expect(optimizations[0].priority).toBe('high'); }); it('should not flag foreign keys with indexes', () => { const postsTable = new TableInfo( 'posts', 'table', [new Column('id', 'INTEGER', true), new Column('user_id', 'INTEGER')], [new Index('idx_user_id', 'posts', ['user_id'])], [new ForeignKey('posts', 'user_id', 'users', 'id')] ); const relationships = [new Relationship('posts', 'user_id', 'users', 'id')]; const optimizations = service.checkMissingIndexes([postsTable], relationships); expect(optimizations.length).toBe(0); }); it('should check multiple foreign keys', () => { const commentsTable = new TableInfo( 'comments', 'table', [ new Column('id', 'INTEGER', true), new Column('post_id', 'INTEGER'), new Column('user_id', 'INTEGER'), ], [], [ new ForeignKey('comments', 'post_id', 'posts', 'id'), new ForeignKey('comments', 'user_id', 'users', 'id'), ] ); const relationships = [ new Relationship('comments', 'post_id', 'posts', 'id'), new Relationship('comments', 'user_id', 'users', 'id'), ]; const optimizations = service.checkMissingIndexes([commentsTable], relationships); expect(optimizations.length).toBe(2); }); }); describe('checkNullableForeignKeys()', () => { it('should detect nullable foreign keys', () => { const postsTable = new TableInfo( 'posts', 'table', [ new Column('id', 'INTEGER', true, false), new Column('user_id', 'INTEGER', false, true), // Nullable! ], [], [new ForeignKey('posts', 'user_id', 'users', 'id')] ); const optimizations = service.checkNullableForeignKeys([postsTable]); expect(optimizations.length).toBe(1); expect(optimizations[0].type).toBe('nullable_foreign_key'); expect(optimizations[0].priority).toBe('medium'); expect(optimizations[0].column).toBe('user_id'); }); it('should not flag non-nullable foreign keys', () => { const postsTable = new TableInfo( 'posts', 'table', [ new Column('id', 'INTEGER', true, false), new Column('user_id', 'INTEGER', false, false), // NOT nullable ], [], [new ForeignKey('posts', 'user_id', 'users', 'id')] ); const optimizations = service.checkNullableForeignKeys([postsTable]); expect(optimizations.length).toBe(0); }); }); describe('checkRedundantIndexes()', () => { it('should detect redundant single-column index', () => { const table = new TableInfo( 'posts', 'table', [new Column('id', 'INTEGER', true), new Column('user_id', 'INTEGER')], [ new Index('idx_user_id', 'posts', ['user_id']), new Index('idx_user_id_created', 'posts', ['user_id', 'created_at']), ] ); const optimizations = service.checkRedundantIndexes(table); expect(optimizations.length).toBeGreaterThan(0); expect(optimizations[0].type).toBe('redundant_index'); expect(optimizations[0].priority).toBe('low'); }); it('should not flag non-redundant indexes', () => { const table = new TableInfo( 'posts', 'table', [new Column('id', 'INTEGER', true), new Column('user_id', 'INTEGER')], [ new Index('idx_user_id', 'posts', ['user_id']), new Index('idx_created', 'posts', ['created_at']), ] ); const optimizations = service.checkRedundantIndexes(table); expect(optimizations.length).toBe(0); }); }); describe('analyzeSchema()', () => { it('should combine all optimization checks', () => { const noPkTable = new TableInfo('logs', 'table', [new Column('message', 'TEXT')]); const postsTable = new TableInfo( 'posts', 'table', [new Column('id', 'INTEGER', true), new Column('user_id', 'INTEGER', false, true)], [], [new ForeignKey('posts', 'user_id', 'users', 'id')] ); const relationships = [new Relationship('posts', 'user_id', 'users', 'id')]; const optimizations = service.analyzeSchema([noPkTable, postsTable], relationships); // Should find: missing PK, missing index, nullable FK expect(optimizations.length).toBeGreaterThanOrEqual(3); }); }); describe('filterByPriority()', () => { it('should filter high priority optimizations', () => { const opts = [ new Optimization('missing_primary_key', 't1', 'r', 's', 'high'), new Optimization('nullable_foreign_key', 't2', 'r', 's', 'medium'), new Optimization('missing_index', 't3', 'r', 's', 'high'), ]; const high = service.filterByPriority(opts, 'high'); expect(high.length).toBe(2); expect(high.every((opt) => opt.priority === 'high')).toBe(true); }); }); describe('filterByType()', () => { it('should filter by optimization type', () => { const opts = [ new Optimization('missing_index', 't1', 'r', 's', 'high'), new Optimization('missing_primary_key', 't2', 'r', 's', 'high'), new Optimization('missing_index', 't3', 'r', 's', 'high'), ]; const indexOpts = service.filterByType(opts, 'missing_index'); expect(indexOpts.length).toBe(2); expect(indexOpts.every((opt) => opt.type === 'missing_index')).toBe(true); }); }); describe('filterByTable()', () => { it('should filter by table name', () => { const opts = [ new Optimization('missing_index', 'posts', 'r', 's', 'high'), new Optimization('missing_primary_key', 'logs', 'r', 's', 'high'), new Optimization('nullable_foreign_key', 'posts', 'r', 's', 'medium'), ]; const postsOpts = service.filterByTable(opts, 'posts'); expect(postsOpts.length).toBe(2); expect(postsOpts.every((opt) => opt.table === 'posts')).toBe(true); }); }); describe('sortByPriority()', () => { it('should sort by priority (high -> medium -> low)', () => { const opts = [ new Optimization('redundant_index', 't1', 'r', 's', 'low'), new Optimization('missing_index', 't2', 'r', 's', 'high'), new Optimization('nullable_foreign_key', 't3', 'r', 's', 'medium'), ]; const sorted = service.sortByPriority(opts); expect(sorted[0].priority).toBe('high'); expect(sorted[1].priority).toBe('medium'); expect(sorted[2].priority).toBe('low'); }); it('should not mutate original array', () => { const opts = [ new Optimization('redundant_index', 't1', 'r', 's', 'low'), new Optimization('missing_index', 't2', 'r', 's', 'high'), ]; const original = [...opts]; service.sortByPriority(opts); expect(opts).toEqual(original); }); }); describe('getSummary()', () => { it('should provide optimization summary', () => { const opts = [ new Optimization('missing_index', 't1', 'r', 's', 'high'), new Optimization('missing_index', 't2', 'r', 's', 'high'), new Optimization('missing_primary_key', 't3', 'r', 's', 'high'), new Optimization('nullable_foreign_key', 't4', 'r', 's', 'medium'), new Optimization('redundant_index', 't5', 'r', 's', 'low'), ]; const summary = service.getSummary(opts); expect(summary.total).toBe(5); expect(summary.byPriority.high).toBe(3); expect(summary.byPriority.medium).toBe(1); expect(summary.byPriority.low).toBe(1); expect(summary.byType.get('missing_index')).toBe(2); expect(summary.byType.get('missing_primary_key')).toBe(1); }); }); });

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