import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { LookerRuntimeClient } from '../../../../../src/context/ingest/adapters/looker/fetch.js'; import { LookerSourceAdapter } from '../../../../../src/context/ingest/adapters/looker/looker.adapter.js'; const connectionId = '11111111-1111-4111-8111-111111111111'; function makeClient(): LookerRuntimeClient { return { listDashboards: vi.fn().mockResolvedValue([]), getDashboard: vi.fn(), listLooks: vi.fn().mockResolvedValue([]), getLook: vi.fn(), listFolders: vi.fn().mockResolvedValue({ folders: [] }), listUsers: vi.fn().mockResolvedValue([]), listGroups: vi.fn().mockResolvedValue([]), listLookmlModels: vi.fn().mockResolvedValue({ models: [{ name: 'b2b', label: 'B2B', explores: [{ name: 'sales_pipeline', label: 'Sales Pipeline' }] }], }), getExplore: vi.fn().mockResolvedValue({ modelName: 'b2b', exploreName: 'sales_pipeline', label: 'Sales Pipeline', description: null, fields: { dimensions: [], measures: [] }, joins: [], }), }; } describe('LookerSourceAdapter', () => { let stagedDir: string; beforeEach(async () => { stagedDir = await mkdtemp(join(tmpdir(), 'looker-adapter-')); }); afterEach(async () => { await rm(stagedDir, { recursive: true, force: true }); }); it('exposes source="looker" and skillNames=["looker_ingest"]', () => { const adapter = new LookerSourceAdapter({ clientFactory: { createClient: () => makeClient() } }); expect(adapter.source).toBe('looker'); expect(adapter.skillNames).toEqual(['looker_ingest']); }); it('enables context evidence indexing and delegates triage signals', async () => { const adapter = new LookerSourceAdapter({ clientFactory: { createClient: () => makeClient() } }); expect(adapter.evidenceIndexing).toBe('documents'); expect(adapter.triageSupported).toBe(true); await expect(adapter.getTriageSignals?.(stagedDir, 'looker:dashboard:10')).resolves.toMatchObject({ objectType: 'looker_dashboard', }); }); it('fetches, detects, and chunks a runtime bundle through the composed adapter', async () => { const adapter = new LookerSourceAdapter({ clientFactory: { createClient: vi.fn().mockResolvedValue(makeClient()) }, now: () => new Date('2026-04-30T12:30:00.000Z'), }); await mkdir(stagedDir, { recursive: true }); await adapter.fetch({ lookerConnectionId: connectionId }, stagedDir, { connectionId, sourceKey: 'looker' }); expect(await adapter.detect(stagedDir)).toBe(true); expect(await readFile(join(stagedDir, 'explores/b2b/sales_pipeline.json'), 'utf-8')).toContain('sales_pipeline'); const result = await adapter.chunk(stagedDir); expect(result.workUnits.map((wu) => wu.unitKey)).toEqual(['looker-explore-b2b-sales_pipeline']); }); it('passes pull success notifications to the server callback', async () => { const onPullSucceeded = vi.fn().mockResolvedValue(undefined); const adapter = new LookerSourceAdapter({ clientFactory: { createClient: () => makeClient() }, onPullSucceeded, }); const completedAt = new Date('2026-04-30T12:00:00.000Z'); await adapter.onPullSucceeded({ connectionId, sourceKey: 'looker', syncId: 'sync-1', trigger: 'scheduled_pull', completedAt, stagedDir: '/tmp/staged', }); expect(onPullSucceeded).toHaveBeenCalledWith({ connectionId, sourceKey: 'looker', syncId: 'sync-1', trigger: 'scheduled_pull', completedAt, stagedDir: '/tmp/staged', }); }); it('describes incremental fetch scope from the staged scope file', async () => { await mkdir(join(stagedDir, 'dashboards'), { recursive: true }); await writeFile( join(stagedDir, 'looker-scope.json'), JSON.stringify( { mode: 'incremental', knownCurrentRawPaths: ['dashboards/10.json', 'dashboards/11.json'], fetchedRawPaths: ['dashboards/11.json'], }, null, 2, ), ); const adapter = new LookerSourceAdapter({ clientFactory: { createClient: () => makeClient() } }); const scope = await adapter.describeScope(stagedDir); expect(scope.isPathInScope('dashboards/10.json')).toBe(false); expect(scope.isPathInScope('dashboards/11.json')).toBe(true); expect(scope.isPathInScope('dashboards/12.json')).toBe(true); }); });