mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-22 11:51:01 +02:00
154 lines
5.9 KiB
TypeScript
154 lines
5.9 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { chunkLookerStagedDir } from '../../../../../src/context/ingest/adapters/looker/chunk.js';
|
|
import { writeLookerEvidenceDocuments } from '../../../../../src/context/ingest/adapters/looker/evidence-documents.js';
|
|
|
|
async function writeJson(stagedDir: string, relPath: string, value: unknown): Promise<void> {
|
|
const abs = join(stagedDir, relPath);
|
|
await mkdir(join(abs, '..'), { recursive: true });
|
|
await writeFile(abs, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
}
|
|
|
|
async function writeSmallFixture(stagedDir: string): Promise<void> {
|
|
await writeJson(stagedDir, 'sync-config.json', {
|
|
lookerConnectionId: '11111111-1111-4111-8111-111111111111',
|
|
fetchedAt: '2026-04-30T12:30:00.000Z',
|
|
});
|
|
await writeJson(stagedDir, 'lookml_models.json', {
|
|
models: [{ name: 'b2b', label: 'B2B', explores: [{ name: 'sales_pipeline', label: 'Sales Pipeline' }] }],
|
|
});
|
|
await writeJson(stagedDir, 'explores/b2b/sales_pipeline.json', {
|
|
modelName: 'b2b',
|
|
exploreName: 'sales_pipeline',
|
|
label: 'Sales Pipeline',
|
|
description: null,
|
|
fields: { dimensions: [{ name: 'opportunities.id' }], measures: [{ name: 'opportunities.arr' }] },
|
|
joins: [],
|
|
});
|
|
await writeJson(stagedDir, 'dashboards/10.json', {
|
|
lookerId: '10',
|
|
title: 'Sales Pipeline',
|
|
description: null,
|
|
folderId: '7',
|
|
ownerId: '3',
|
|
updatedAt: '2026-04-30T12:00:00.000Z',
|
|
tiles: [{ id: '100', title: 'ARR', lookId: null, query: { model: 'b2b', view: 'sales_pipeline' } }],
|
|
});
|
|
await writeJson(stagedDir, 'looks/20.json', {
|
|
lookerId: '20',
|
|
title: 'Open Pipeline',
|
|
description: null,
|
|
folderId: '7',
|
|
ownerId: '3',
|
|
updatedAt: '2026-04-30T12:00:00.000Z',
|
|
query: { model: 'b2b', view: 'sales_pipeline', fields: ['opportunities.arr'] },
|
|
});
|
|
await writeJson(stagedDir, 'folders/tree.json', {
|
|
folders: [{ id: '7', name: 'Sandbox', parentId: null, path: ['Sandbox'] }],
|
|
});
|
|
await writeJson(stagedDir, 'users/3.json', { id: '3', displayName: 'Ada Lovelace', email: null });
|
|
await writeJson(stagedDir, 'signals/dashboard_usage.json', [
|
|
{ contentId: '10', queryCount30d: 50, uniqueUsers30d: 8 },
|
|
]);
|
|
await writeJson(stagedDir, 'signals/look_usage.json', [{ contentId: '20', queryCount30d: 20, uniqueUsers30d: 5 }]);
|
|
await writeJson(stagedDir, 'signals/scheduled_plans.json', [
|
|
{ contentId: '10', contentType: 'dashboard', isScheduled: true, scheduleCount: 1, recipientCount: 3 },
|
|
]);
|
|
await writeJson(stagedDir, 'signals/favorites.json', [
|
|
{ contentId: '10', contentType: 'dashboard', favoriteCount: 4 },
|
|
]);
|
|
await writeLookerEvidenceDocuments(stagedDir);
|
|
}
|
|
|
|
describe('chunkLookerStagedDir', () => {
|
|
let stagedDir: string;
|
|
|
|
beforeEach(async () => {
|
|
stagedDir = await mkdtemp(join(tmpdir(), 'looker-chunk-'));
|
|
await writeSmallFixture(stagedDir);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(stagedDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it('emits one WU per explore, dashboard, and Look with readable dependencies', async () => {
|
|
const result = await chunkLookerStagedDir(stagedDir);
|
|
expect(result.reconcileNotes).toEqual([
|
|
expect.stringContaining('emit_artifact_resolution with actionType="subsumed"'),
|
|
]);
|
|
expect(result.workUnits.map((wu) => wu.unitKey).sort()).toEqual([
|
|
'looker-dashboard-10',
|
|
'looker-explore-b2b-sales_pipeline',
|
|
'looker-look-20',
|
|
]);
|
|
|
|
const dashboard = result.workUnits.find((wu) => wu.unitKey === 'looker-dashboard-10');
|
|
expect(dashboard?.rawFiles).toEqual([
|
|
'dashboards/10.json',
|
|
'evidence/dashboards/10/metadata.json',
|
|
'evidence/dashboards/10/page.md',
|
|
]);
|
|
expect(dashboard?.notes).toContain('context_candidate_write');
|
|
expect(dashboard?.notes).not.toContain('wiki_write');
|
|
expect(dashboard?.dependencyPaths.sort()).toEqual([
|
|
'explores/b2b/sales_pipeline.json',
|
|
'folders/tree.json',
|
|
'signals/dashboard_usage.json',
|
|
'signals/favorites.json',
|
|
'signals/scheduled_plans.json',
|
|
'users/3.json',
|
|
]);
|
|
|
|
const explore = result.workUnits.find((wu) => wu.unitKey === 'looker-explore-b2b-sales_pipeline');
|
|
expect(explore?.rawFiles).toEqual([
|
|
'explores/b2b/sales_pipeline.json',
|
|
'evidence/explores/b2b/sales_pipeline/metadata.json',
|
|
'evidence/explores/b2b/sales_pipeline/page.md',
|
|
]);
|
|
expect(explore?.dependencyPaths).toEqual(['lookml_models.json']);
|
|
});
|
|
|
|
it('keeps downstream dashboard and Look WUs when an explore dependency changes', async () => {
|
|
const result = await chunkLookerStagedDir(stagedDir, {
|
|
added: [],
|
|
modified: ['explores/b2b/sales_pipeline.json'],
|
|
deleted: [],
|
|
unchanged: [
|
|
'dashboards/10.json',
|
|
'looks/20.json',
|
|
'lookml_models.json',
|
|
'folders/tree.json',
|
|
'users/3.json',
|
|
'signals/dashboard_usage.json',
|
|
'signals/look_usage.json',
|
|
'signals/scheduled_plans.json',
|
|
'signals/favorites.json',
|
|
],
|
|
});
|
|
|
|
expect(result.workUnits.map((wu) => wu.unitKey).sort()).toEqual([
|
|
'looker-dashboard-10',
|
|
'looker-explore-b2b-sales_pipeline',
|
|
'looker-look-20',
|
|
]);
|
|
expect(result.workUnits.find((wu) => wu.unitKey === 'looker-dashboard-10')?.rawFiles).toEqual([
|
|
'dashboards/10.json',
|
|
'evidence/dashboards/10/metadata.json',
|
|
'evidence/dashboards/10/page.md',
|
|
]);
|
|
});
|
|
|
|
it('returns an EvictionUnit for deleted runtime entity raw paths', async () => {
|
|
const result = await chunkLookerStagedDir(stagedDir, {
|
|
added: [],
|
|
modified: [],
|
|
deleted: ['looks/20.json'],
|
|
unchanged: ['dashboards/10.json', 'explores/b2b/sales_pipeline.json'],
|
|
});
|
|
|
|
expect(result.eviction).toEqual({ deletedRawPaths: ['looks/20.json'] });
|
|
});
|
|
});
|