mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-25 08:48:08 +02:00
118 lines
5.9 KiB
TypeScript
118 lines
5.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildLookmlGraph } from '../../../../../src/context/ingest/adapters/lookml/graph.js';
|
|
import type { ParsedLookmlProject } from '../../../../../src/context/ingest/adapters/lookml/parse.js';
|
|
|
|
type LooseParsedLookmlProject = Omit<Partial<ParsedLookmlProject>, 'models' | 'views'> & {
|
|
models?: Array<Omit<ParsedLookmlProject['models'][number], 'connectionName'> & { connectionName?: string | null }>;
|
|
views?: Array<Omit<ParsedLookmlProject['views'][number], 'rawSqlTableName'> & { rawSqlTableName?: string | null }>;
|
|
};
|
|
|
|
const mkProject = (overrides: LooseParsedLookmlProject): ParsedLookmlProject => ({
|
|
dashboards: [],
|
|
allPaths: [],
|
|
...overrides,
|
|
models: (overrides.models ?? []).map((model) => ({ connectionName: null, ...model })),
|
|
views: (overrides.views ?? []).map((view) => ({ rawSqlTableName: null, ...view })),
|
|
});
|
|
|
|
describe('buildLookmlGraph', () => {
|
|
it('assigns a single model as owner of all its included views', () => {
|
|
const project = mkProject({
|
|
models: [{ path: 'orders.model.lkml', name: 'orders', includes: ['views/*.view.lkml'], explores: ['orders'] }],
|
|
views: [
|
|
{ path: 'views/orders.view.lkml', name: 'orders', extendsFrom: [] },
|
|
{ path: 'views/customers.view.lkml', name: 'customers', extendsFrom: [] },
|
|
],
|
|
allPaths: ['orders.model.lkml', 'views/customers.view.lkml', 'views/orders.view.lkml'],
|
|
});
|
|
const graph = buildLookmlGraph(project);
|
|
expect(graph.ownerByViewPath.get('views/orders.view.lkml')).toBe('orders');
|
|
expect(graph.ownerByViewPath.get('views/customers.view.lkml')).toBe('orders');
|
|
expect(graph.viewsIncludedByModel.get('orders')?.sort()).toEqual([
|
|
'views/customers.view.lkml',
|
|
'views/orders.view.lkml',
|
|
]);
|
|
});
|
|
|
|
it('assigns shared views to the lexicographically-first model that includes them', () => {
|
|
const project = mkProject({
|
|
models: [
|
|
{ path: 'marketing.model.lkml', name: 'marketing', includes: ['views/shared.view.lkml'], explores: [] },
|
|
{
|
|
path: 'orders.model.lkml',
|
|
name: 'orders',
|
|
includes: ['views/shared.view.lkml', 'views/orders.view.lkml'],
|
|
explores: [],
|
|
},
|
|
],
|
|
views: [
|
|
{ path: 'views/shared.view.lkml', name: 'shared', extendsFrom: [] },
|
|
{ path: 'views/orders.view.lkml', name: 'orders', extendsFrom: [] },
|
|
],
|
|
allPaths: ['marketing.model.lkml', 'orders.model.lkml', 'views/orders.view.lkml', 'views/shared.view.lkml'],
|
|
});
|
|
const graph = buildLookmlGraph(project);
|
|
// "marketing" sorts before "orders", so marketing owns the shared view.
|
|
expect(graph.ownerByViewPath.get('views/shared.view.lkml')).toBe('marketing');
|
|
expect(graph.ownerByViewPath.get('views/orders.view.lkml')).toBe('orders');
|
|
// Both models list the shared view in their include set:
|
|
expect(graph.includersByViewPath.get('views/shared.view.lkml')?.sort()).toEqual(['marketing', 'orders']);
|
|
});
|
|
|
|
it('resolves transitive extends chains into dependency paths', () => {
|
|
const project = mkProject({
|
|
models: [{ path: 'orders.model.lkml', name: 'orders', includes: ['views/*.view.lkml'], explores: [] }],
|
|
views: [
|
|
{ path: 'views/base.view.lkml', name: 'base', extendsFrom: [] },
|
|
{ path: 'views/orders.view.lkml', name: 'orders', extendsFrom: ['base'] },
|
|
{ path: 'views/orders_ext.view.lkml', name: 'orders_ext', extendsFrom: ['orders'] },
|
|
],
|
|
allPaths: ['orders.model.lkml', 'views/base.view.lkml', 'views/orders.view.lkml', 'views/orders_ext.view.lkml'],
|
|
});
|
|
const graph = buildLookmlGraph(project);
|
|
expect(graph.extendsAncestorsByViewName.get('orders_ext')?.sort()).toEqual(['base', 'orders']);
|
|
expect(graph.extendsAncestorsByViewName.get('orders')?.sort()).toEqual(['base']);
|
|
expect(graph.extendsAncestorsByViewName.get('base')?.sort()).toEqual([]);
|
|
});
|
|
|
|
it('resolves glob-style include patterns (views/*.view.lkml) against allPaths', () => {
|
|
const project = mkProject({
|
|
models: [{ path: 'orders.model.lkml', name: 'orders', includes: ['views/*.view.lkml'], explores: [] }],
|
|
views: [
|
|
{ path: 'views/a.view.lkml', name: 'a', extendsFrom: [] },
|
|
{ path: 'views/sub/b.view.lkml', name: 'b', extendsFrom: [] },
|
|
],
|
|
allPaths: ['orders.model.lkml', 'views/a.view.lkml', 'views/sub/b.view.lkml'],
|
|
});
|
|
const graph = buildLookmlGraph(project);
|
|
// Single-star glob matches one path segment — "views/sub/b.view.lkml" is NOT matched.
|
|
expect(graph.viewsIncludedByModel.get('orders')?.sort()).toEqual(['views/a.view.lkml']);
|
|
});
|
|
|
|
it('resolves double-star include patterns (views/**/*.view.lkml) recursively', () => {
|
|
const project = mkProject({
|
|
models: [{ path: 'orders.model.lkml', name: 'orders', includes: ['views/**/*.view.lkml'], explores: [] }],
|
|
views: [
|
|
{ path: 'views/a.view.lkml', name: 'a', extendsFrom: [] },
|
|
{ path: 'views/sub/b.view.lkml', name: 'b', extendsFrom: [] },
|
|
],
|
|
allPaths: ['orders.model.lkml', 'views/a.view.lkml', 'views/sub/b.view.lkml'],
|
|
});
|
|
const graph = buildLookmlGraph(project);
|
|
expect(graph.viewsIncludedByModel.get('orders')?.sort()).toEqual(['views/a.view.lkml', 'views/sub/b.view.lkml']);
|
|
});
|
|
|
|
it('leaves a view ownerless when no model includes it', () => {
|
|
const project = mkProject({
|
|
models: [{ path: 'other.model.lkml', name: 'other', includes: ['views/included.view.lkml'], explores: [] }],
|
|
views: [
|
|
{ path: 'views/included.view.lkml', name: 'included', extendsFrom: [] },
|
|
{ path: 'views/orphan.view.lkml', name: 'orphan', extendsFrom: [] },
|
|
],
|
|
allPaths: ['other.model.lkml', 'views/included.view.lkml', 'views/orphan.view.lkml'],
|
|
});
|
|
const graph = buildLookmlGraph(project);
|
|
expect(graph.ownerByViewPath.has('views/orphan.view.lkml')).toBe(false);
|
|
expect(graph.ownerByViewPath.get('views/included.view.lkml')).toBe('other');
|
|
});
|
|
});
|