refactor: remove legacy ktx compatibility shims (#211)

* refactor: remove legacy ktx compatibility shims

* fix: restore overlay collision guidance
This commit is contained in:
Andrey Avtomonov 2026-05-24 16:57:23 +02:00 committed by GitHub
parent a954a29a76
commit 96952fb43c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 294 additions and 342 deletions

View file

@ -466,7 +466,43 @@ describe('createKtxMcpServer', () => {
});
});
it('sl_query normalizes order_by from cube-style {id, desc} and bare strings to {field, direction}', async () => {
it('sl_query rejects cube-style order_by aliases and bare strings', async () => {
const fake = makeFakeServer();
const semanticLayer: KtxSemanticLayerMcpPort = {
readSource: vi.fn(),
query: vi.fn<KtxSemanticLayerMcpPort['query']>().mockResolvedValue({
sql: '',
headers: [],
rows: [],
totalRows: 0,
}),
};
createKtxMcpServer({
server: fake.server,
userContext: { userId: 'local-user' },
contextTools: { semanticLayer },
});
await expect(
getTool(fake.tools, 'sl_query').handler({
connectionId: 'warehouse',
measures: ['orders.count'],
order_by: [{ id: 'orders.quarter_label', desc: false }],
}),
).resolves.toMatchObject({ isError: true });
await expect(
getTool(fake.tools, 'sl_query').handler({
connectionId: 'warehouse',
measures: ['orders.count'],
order_by: ['orders.segment'],
}),
).resolves.toMatchObject({ isError: true });
expect(semanticLayer.query).not.toHaveBeenCalled();
});
it('sl_query accepts canonical order_by entries', async () => {
const fake = makeFakeServer();
const semanticLayer: KtxSemanticLayerMcpPort = {
readSource: vi.fn(),
@ -489,9 +525,7 @@ describe('createKtxMcpServer', () => {
measures: ['orders.count'],
order_by: [
{ field: 'orders.total', direction: 'desc' },
{ id: 'orders.quarter_label', desc: false },
{ id: 'orders.created_at', desc: true },
'orders.segment',
{ field: 'orders.segment' },
],
});
@ -501,8 +535,6 @@ describe('createKtxMcpServer', () => {
query: expect.objectContaining({
order_by: [
{ field: 'orders.total', direction: 'desc' },
{ field: 'orders.quarter_label', direction: 'asc' },
{ field: 'orders.created_at', direction: 'desc' },
{ field: 'orders.segment', direction: 'asc' },
],
}),
@ -511,7 +543,35 @@ describe('createKtxMcpServer', () => {
);
});
it('sl_query normalizes cube-style dimensions to field dimensions', async () => {
it('sl_query rejects cube-style dimensions and bare strings', async () => {
const fake = makeFakeServer();
const semanticLayer = makeAllContextTools().semanticLayer!;
createKtxMcpServer({
server: fake.server,
userContext: { userId: 'local-user' },
contextTools: { semanticLayer },
});
await expect(
getTool(fake.tools, 'sl_query').handler({
connectionId: 'warehouse',
measures: ['orders.count'],
dimensions: [{ dimension: 'orders.created_at', granularity: 'month' }],
}),
).resolves.toMatchObject({ isError: true });
await expect(
getTool(fake.tools, 'sl_query').handler({
connectionId: 'warehouse',
measures: ['orders.count'],
dimensions: ['orders.status'],
}),
).resolves.toMatchObject({ isError: true });
expect(semanticLayer.query).not.toHaveBeenCalled();
});
it('sl_query accepts canonical field dimensions', async () => {
const fake = makeFakeServer();
const semanticLayer = makeAllContextTools().semanticLayer!;
@ -524,7 +584,7 @@ describe('createKtxMcpServer', () => {
await getTool(fake.tools, 'sl_query').handler({
connectionId: 'warehouse',
measures: ['orders.count'],
dimensions: [{ dimension: 'orders.created_at', granularity: 'month' }, 'orders.status'],
dimensions: [{ field: 'orders.created_at', granularity: 'month' }, { field: 'orders.status' }],
});
expect(semanticLayer.query).toHaveBeenCalledWith(
@ -538,7 +598,27 @@ describe('createKtxMcpServer', () => {
);
});
it('entity_details normalizes sql-style schema table refs', async () => {
it('entity_details rejects sql-style schema table ref aliases', async () => {
const fake = makeFakeServer();
const entityDetails = makeAllContextTools().entityDetails!;
createKtxMcpServer({
server: fake.server,
userContext: { userId: 'local-user' },
contextTools: { entityDetails },
});
await expect(
getTool(fake.tools, 'entity_details').handler({
connectionId: 'warehouse',
entities: [{ table: { schema: 'public', table: 'orders' }, columns: ['id'] }],
}),
).resolves.toMatchObject({ isError: true });
expect(entityDetails.read).not.toHaveBeenCalled();
});
it('entity_details accepts canonical table refs', async () => {
const fake = makeFakeServer();
const entityDetails = makeAllContextTools().entityDetails!;
@ -550,7 +630,7 @@ describe('createKtxMcpServer', () => {
await getTool(fake.tools, 'entity_details').handler({
connectionId: 'warehouse',
entities: [{ table: { schema: 'public', table: 'orders' }, columns: ['id'] }],
entities: [{ table: { catalog: null, db: 'public', name: 'orders' }, columns: ['id'] }],
});
expect(entityDetails.read).toHaveBeenCalledWith({
@ -1018,7 +1098,7 @@ describe('createKtxMcpServer', () => {
await getTool(fake.tools, 'sl_query').handler({
connectionId: '00000000-0000-4000-8000-000000000001',
measures: ['orders.count'],
dimensions: ['orders.created_at'],
dimensions: [{ field: 'orders.created_at' }],
filters: ['orders.status = paid'],
limit: 25,
});