mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-10 11:12:10 +02:00
145 lines
3.9 KiB
TypeScript
145 lines
3.9 KiB
TypeScript
|
|
import { describe, expect, it } from 'vitest';
|
||
|
|
import {
|
||
|
|
assertRawSqlAllowed,
|
||
|
|
connectionQueryPolicy,
|
||
|
|
projectAllowsRawSql,
|
||
|
|
restrictedFederatedMemberIds,
|
||
|
|
} from '../../../src/context/connections/query-policy.js';
|
||
|
|
import { parseKtxProjectConfig } from '../../../src/context/project/config.js';
|
||
|
|
import { KtxQueryError } from '../../../src/errors.js';
|
||
|
|
|
||
|
|
const PROJECT_DIR = '/tmp/proj';
|
||
|
|
|
||
|
|
function config(yaml: string) {
|
||
|
|
return parseKtxProjectConfig(yaml);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('connectionQueryPolicy', () => {
|
||
|
|
it('defaults to read-only-sql when the field is absent or the connection is unknown', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
warehouse:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:warehouse.db
|
||
|
|
`);
|
||
|
|
expect(connectionQueryPolicy(parsed.connections.warehouse)).toBe('read-only-sql');
|
||
|
|
expect(connectionQueryPolicy(undefined)).toBe('read-only-sql');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('reads semantic-layer-only from ktx.yaml', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
warehouse:
|
||
|
|
driver: snowflake
|
||
|
|
url: env:SNOWFLAKE_URL
|
||
|
|
query_policy: semantic-layer-only
|
||
|
|
`);
|
||
|
|
expect(connectionQueryPolicy(parsed.connections.warehouse)).toBe('semantic-layer-only');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects unknown query_policy values at config parse time', () => {
|
||
|
|
expect(() =>
|
||
|
|
config(`
|
||
|
|
connections:
|
||
|
|
warehouse:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:warehouse.db
|
||
|
|
query_policy: everything-goes
|
||
|
|
`),
|
||
|
|
).toThrow();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('assertRawSqlAllowed', () => {
|
||
|
|
it('allows raw SQL on an unrestricted connection', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
warehouse:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:warehouse.db
|
||
|
|
`);
|
||
|
|
expect(() => assertRawSqlAllowed(parsed, PROJECT_DIR, 'warehouse')).not.toThrow();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects raw SQL on a restricted connection with an expected error naming the policy', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
warehouse:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:warehouse.db
|
||
|
|
query_policy: semantic-layer-only
|
||
|
|
`);
|
||
|
|
expect(() => assertRawSqlAllowed(parsed, PROJECT_DIR, 'warehouse')).toThrow(KtxQueryError);
|
||
|
|
expect(() => assertRawSqlAllowed(parsed, PROJECT_DIR, 'warehouse')).toThrow(
|
||
|
|
/query_policy: semantic-layer-only/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects federated raw SQL when any member connection is restricted', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
sales:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:sales.db
|
||
|
|
query_policy: semantic-layer-only
|
||
|
|
events:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:events.db
|
||
|
|
`);
|
||
|
|
expect(restrictedFederatedMemberIds(parsed, PROJECT_DIR)).toEqual(['sales']);
|
||
|
|
expect(() => assertRawSqlAllowed(parsed, PROJECT_DIR, '_ktx_federated')).toThrow(/"sales"/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('allows federated raw SQL when no member is restricted', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
sales:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:sales.db
|
||
|
|
events:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:events.db
|
||
|
|
`);
|
||
|
|
expect(restrictedFederatedMemberIds(parsed, PROJECT_DIR)).toEqual([]);
|
||
|
|
expect(() => assertRawSqlAllowed(parsed, PROJECT_DIR, '_ktx_federated')).not.toThrow();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('projectAllowsRawSql', () => {
|
||
|
|
it('is true when at least one SQL connection is unrestricted', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
finance:
|
||
|
|
driver: postgres
|
||
|
|
url: env:FINANCE_URL
|
||
|
|
query_policy: semantic-layer-only
|
||
|
|
warehouse:
|
||
|
|
driver: sqlite
|
||
|
|
url: file:warehouse.db
|
||
|
|
`);
|
||
|
|
expect(projectAllowsRawSql(parsed)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('is false when every SQL connection is restricted', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
finance:
|
||
|
|
driver: postgres
|
||
|
|
url: env:FINANCE_URL
|
||
|
|
query_policy: semantic-layer-only
|
||
|
|
`);
|
||
|
|
expect(projectAllowsRawSql(parsed)).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('is true for projects with no SQL-queryable connections', () => {
|
||
|
|
const parsed = config(`
|
||
|
|
connections:
|
||
|
|
docs:
|
||
|
|
driver: mongodb
|
||
|
|
url: mongodb://localhost:27017/app
|
||
|
|
`);
|
||
|
|
expect(projectAllowsRawSql(parsed)).toBe(true);
|
||
|
|
expect(projectAllowsRawSql(config('connections: {}'))).toBe(true);
|
||
|
|
});
|
||
|
|
});
|