service.test.ts•3.01 kB
import { describe, test, expect, vi, beforeEach } from 'vitest'
import { MetadataService } from '../service.js'
import { ICache } from '../../../core/cache/interface.js'
import { DatabaseConnector } from '../../../core/types/connector.js'
function makeCache(): ICache {
  const store = new Map<string, any>()
  const ns = 'metadata:'
  const api: ICache = {
    withNamespace: (n: string) => api,
    get: vi.fn(async (k: string) => store.get(k)),
    set: vi.fn(async (k: string, v: any) => { store.set(k, v) }),
    has: vi.fn(async (k: string) => store.has(k)),
    delete: vi.fn(async (k: string) => { store.delete(k) ; return true }),
    deleteMany: vi.fn(async (keys: string[]) => { keys.forEach(k => store.delete(k)) }),
    clear: vi.fn(async () => { store.clear() }),
    getStats: vi.fn(async () => ({ hits: 0, misses: 0, size: store.size, keys: Array.from(store.keys()) }))
  } as any
  return api
}
function makeConnector(): DatabaseConnector {
  return {
    connect: vi.fn(),
    disconnect: vi.fn(),
    isConnected: vi.fn(() => true),
    ping: vi.fn(async () => true),
    getDatabases: vi.fn(async () => ['db1', 'db2']),
    getTables: vi.fn(async () => ['t1', 't2', 'user_accounts']),
    getTableSchema: vi.fn(async (t: string) => ({ name: t, columns: [], comment: '' } as any)),
    getTableIndexes: vi.fn(async () => []),
    getTableConstraints: vi.fn(async () => []),
    getTableRelations: vi.fn(async () => []),
    getSampleData: vi.fn(),
    executeReadQuery: vi.fn()
  }
}
describe('MetadataService', () => {
  let cache: ICache
  let connector: DatabaseConnector
  let svc: MetadataService
  beforeEach(() => {
    cache = makeCache()
    connector = makeConnector()
    svc = new MetadataService(connector, cache)
  })
  test('getDatabases uses cache', async () => {
    const first = await svc.getDatabases()
    expect(first).toEqual(['db1', 'db2'])
    ;(connector.getDatabases as any).mockResolvedValue(['x'])
    const second = await svc.getDatabases()
    expect(second).toEqual(['db1', 'db2'])
  })
  test('getTables supports pattern filter', async () => {
    const all = await svc.getTables('db1')
    expect(all).toEqual(['t1', 't2', 'user_accounts'])
    const filtered = await svc.getTables('db1', 'user%')
    expect(filtered).toEqual(['user_accounts'])
  })
  test('getDatabaseSchema batches in parallel and dedup relations', async () => {
    ;(connector.getTables as any).mockResolvedValue(['a', 'b'])
    ;(connector.getTableRelations as any).mockResolvedValueOnce([{ constraintName: 'fk1' }]).mockResolvedValueOnce([{ constraintName: 'fk1' }])
    const res = await svc.getDatabaseSchema('db1')
    expect(res.tables.length).toBe(2)
    expect(res.relations.length).toBe(1)
  })
  test('refreshCache clears table entries', async () => {
    await cache.set('schema:default:foo', {}, 1)
    await cache.set('indexes:default:foo', {}, 1)
    await svc.refreshCache('table', 'foo')
    expect(await cache.get('schema:default:foo')).toBeUndefined()
  })
})