"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const explore_1 = require("../types/explore");
const parameters_1 = require("./parameters");
describe('getParameterReferences', () => {
    describe('Project-level parameters', () => {
        it('should extract project parameter with lightdash.parameters format', () => {
            const sql = 'SELECT * FROM users WHERE status = ${lightdash.parameters.status}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['status']);
        });
        it('should extract project parameter with ld.parameters format', () => {
            const sql = 'SELECT * FROM orders WHERE region = ${ld.parameters.region}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['region']);
        });
        it('should extract multiple different project parameters', () => {
            const sql = 'SELECT * FROM orders WHERE region = ${ld.parameters.region} AND status = ${lightdash.parameters.status} AND created_at > ${ld.parameters.date}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('region');
            expect(result).toContain('status');
            expect(result).toContain('date');
            expect(result.length).toBe(3);
        });
        it('should return unique parameter names when duplicates exist', () => {
            const sql = 'SELECT * FROM users WHERE status = ${lightdash.parameters.status} AND created_by = ${lightdash.parameters.status}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['status']);
        });
    });
    describe('Model-level parameters', () => {
        it('should extract model parameter with lightdash.parameters format', () => {
            const sql = 'SELECT * FROM users WHERE name = ${lightdash.parameters.customers.customer_name}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['customers.customer_name']);
        });
        it('should extract model parameter with ld.parameters format', () => {
            const sql = 'SELECT * FROM orders WHERE status = ${ld.parameters.orders.status}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['orders.status']);
        });
        it('should extract multiple model parameters', () => {
            const sql = 'SELECT * FROM orders o JOIN customers c ON c.id = o.customer_id WHERE c.name = ${ld.parameters.customers.customer_name} AND o.status = ${lightdash.parameters.orders.status}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('customers.customer_name');
            expect(result).toContain('orders.status');
            expect(result.length).toBe(2);
        });
        it('should return unique model parameter names when duplicates exist', () => {
            const sql = 'SELECT * FROM users WHERE name = ${lightdash.parameters.customers.customer_name} AND created_by = ${ld.parameters.customers.customer_name}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['customers.customer_name']);
        });
    });
    describe('Mixed project and model parameters', () => {
        it('should handle project and model parameters in same SQL', () => {
            const sql = 'SELECT * FROM users WHERE region = ${ld.parameters.region} AND name = ${ld.parameters.customers.customer_name}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('region');
            expect(result).toContain('customers.customer_name');
            expect(result.length).toBe(2);
        });
        it('should extract parameters in CASE statements', () => {
            const sql = 'SELECT CASE WHEN status = ${ld.parameters.orders.active_status} THEN 1 ELSE 0 END FROM orders WHERE region = ${ld.parameters.region}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('orders.active_status');
            expect(result).toContain('region');
            expect(result.length).toBe(2);
        });
        it('should extract parameters in JOIN conditions', () => {
            const sql = 'SELECT * FROM orders o JOIN customers c ON c.id = o.customer_id AND c.region = ${lightdash.parameters.customers.target_region} AND o.status = ${ld.parameters.active_status}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('customers.target_region');
            expect(result).toContain('active_status');
            expect(result.length).toBe(2);
        });
    });
    describe('Liquid template references', () => {
        it('should extract parameters from {% if %} liquid tags', () => {
            const sql = '{% if ld.parameters.source_schema == "jaffle" %}jaffle.orders{% else %}public.orders{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['source_schema']);
        });
        it('should extract parameters from {% elsif %} branches', () => {
            const sql = '{% if ld.parameters.grain == "day" %}d' +
                '{% elsif ld.parameters.fallback == "week" %}w' +
                '{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('grain');
            expect(result).toContain('fallback');
            expect(result.length).toBe(2);
        });
        it('should extract parameters from {% case %} / {% when %} blocks', () => {
            const sql = '{% case ld.parameters.region %}{% when "EU" %}eu{% when "US" %}us{% endcase %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['region']);
        });
        it('should extract parameters from {% assign %} tags', () => {
            const sql = '{% assign foo = ld.parameters.bar %}SELECT {{ foo }}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['bar']);
        });
        it('should extract parameters used with liquid operators (contains)', () => {
            const sql = '{% if ld.parameters.tags contains "premium" %}premium{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['tags']);
        });
        it('should extract parameters from lightdash.parameters in liquid tags', () => {
            const sql = '{% if lightdash.parameters.grain == "day" %}day{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['grain']);
        });
        it('should extract model-scoped parameters from liquid tags', () => {
            const sql = '{% if ld.parameters.orders.status == "active" %}active{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['orders.status']);
        });
        it('should NOT detect ld.query.fields as a parameter', () => {
            const sql = '{% if ld.query.fields contains "orders.id" %}has_id{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual([]);
        });
        it('should extract both ${} and liquid-bare references in the same string', () => {
            const sql = '{% if ld.parameters.region == "EU" %}SELECT * FROM eu_orders{% else %}SELECT * FROM ${ld.parameters.fallback_table}{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('region');
            expect(result).toContain('fallback_table');
            expect(result.length).toBe(2);
        });
        it('should accept multiple sources and dedupe', () => {
            const sql = '${ld.parameters.x}';
            const format = '${ld.parameters.x}${ld.parameters.y}';
            const result = (0, parameters_1.getParameterReferences)(sql, format);
            expect(result).toContain('x');
            expect(result).toContain('y');
            expect(result.length).toBe(2);
        });
        it('should ignore undefined sources', () => {
            const result = (0, parameters_1.getParameterReferences)('${ld.parameters.x}', undefined);
            expect(result).toEqual(['x']);
        });
    });
    describe('Negative cases — false-positive guards', () => {
        it('should not match bare ld.parameters.X when no liquid tags are present', () => {
            // Without {%, the broad scan never fires — only ${...} is matched.
            const sql = 'SELECT * FROM tbl -- ld.parameters.fake_ref in a comment';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual([]);
        });
        it('should not match suffixed identifiers like myld.parameters.x in liquid SQL', () => {
            // Left boundary in the broad regex prevents matching after a word/dot char.
            const sql = '{% if ld.parameters.real == 1 %}SELECT myld.parameters.fake{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['real']);
        });
        it('should not match if the left boundary is another dot (e.g. table.ld.parameters.x)', () => {
            const sql = '{% if ld.parameters.real == 1 %}SELECT t.ld.parameters.fake{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['real']);
        });
        it('limitation: a quoted SQL identifier "ld.parameters.fake" inside liquid SQL IS still matched', () => {
            // Documented behavior: column names with literal `ld.parameters.X` are
            // exceedingly rare (warehouses require quoting for dots in identifiers)
            // and a false-positive in detection is harmless (UI shows extra param).
            const sql = 'SELECT "ld.parameters.fake" FROM t WHERE {% if ld.parameters.real %}1{% endif %}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('real');
            expect(result).toContain('fake');
        });
    });
    describe('Edge cases and validation', () => {
        it('should return empty array when no parameter references exist', () => {
            const sql = 'SELECT * FROM users WHERE status = "active"';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual([]);
        });
        it('should handle empty parameter names gracefully', () => {
            const sql = 'SELECT * FROM users WHERE status = ${ld.parameters.}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual([]);
        });
        it('should handle malformed parameter syntax', () => {
            const sql = 'SELECT * FROM users WHERE status = ${ld.parameters';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual([]);
        });
        it('should reject parameters with invalid characters', () => {
            const sql = 'SELECT * FROM users WHERE status = ${ld.parameters.users.status-invalid}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual([]);
        });
        it('should reject parameters with too many dots', () => {
            const sql = 'SELECT * FROM users WHERE status = ${ld.parameters.users.status.extra}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual([]);
        });
        it('should filter out invalid parameters but keep valid ones', () => {
            const sql = 'SELECT * FROM users WHERE valid = ${ld.parameters.users.status} AND invalid = ${ld.parameters.users.status.extra}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['users.status']);
        });
    });
    describe('Performance scenarios', () => {
        it('should handle large SQL strings efficiently', () => {
            const baseSQL = 'SELECT * FROM users WHERE ';
            const conditions = Array.from({ length: 50 }, (_, i) => `field${i} = \${ld.parameters.table${i}.param${i}}`).join(' AND ');
            const sql = baseSQL + conditions;
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result.length).toBe(50);
            expect(result[0]).toBe('table0.param0');
            expect(result[49]).toBe('table49.param49');
        });
        it('should handle SQL with many duplicate parameter references', () => {
            const conditions = Array.from({ length: 20 }, () => 'status = ${ld.parameters.users.status}').join(' OR ');
            const sql = `SELECT * FROM users WHERE ${conditions}`;
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toEqual(['users.status']);
        });
    });
    describe('Parameter scoping and name conflicts', () => {
        it('should distinguish between project and model parameters with same name', () => {
            const sql = 'SELECT * FROM users WHERE region = ${ld.parameters.region} AND user_region = ${ld.parameters.users.region}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('region'); // project-level
            expect(result).toContain('users.region'); // model-level
            expect(result.length).toBe(2);
        });
        it('should handle multiple same-named parameters across different scopes', () => {
            const sql = 'SELECT * FROM orders o JOIN customers c ON c.id = o.customer_id WHERE ' +
                'o.status = ${ld.parameters.status} AND ' + // project-level status
                'o.order_status = ${ld.parameters.orders.status} AND ' + // model-level orders.status
                'c.customer_status = ${lightdash.parameters.customers.status}'; // model-level customers.status
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('status'); // project-level
            expect(result).toContain('orders.status'); // model-level
            expect(result).toContain('customers.status'); // model-level
            expect(result.length).toBe(3);
        });
        it('should correctly scope parameters in complex JOIN conditions', () => {
            const sql = 'SELECT * FROM orders o ' +
                'LEFT JOIN customers c ON c.id = o.customer_id AND c.region = ${ld.parameters.region} ' + // project-level
                'LEFT JOIN products p ON p.id = o.product_id AND p.category = ${ld.parameters.products.category} ' + // model-level
                'WHERE o.region = ${lightdash.parameters.orders.region}'; // model-level with same name as project
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('region'); // project-level
            expect(result).toContain('products.category'); // model-level
            expect(result).toContain('orders.region'); // model-level with same name
            expect(result.length).toBe(3);
        });
        it('should handle same parameter name across multiple model scopes', () => {
            const sql = 'SELECT * FROM orders o JOIN customers c ON c.id = o.customer_id WHERE ' +
                'o.created_date >= ${ld.parameters.orders.start_date} AND ' +
                'c.created_date >= ${lightdash.parameters.customers.start_date}';
            const result = (0, parameters_1.getParameterReferences)(sql);
            expect(result).toContain('orders.start_date');
            expect(result).toContain('customers.start_date');
            expect(result.length).toBe(2);
        });
    });
});
describe('validateParameterNames', () => {
    it('should accept valid parameter names (alphanumeric, underscores, hyphens)', () => {
        const result = (0, parameters_1.validateParameterNames)({
            status: { label: 'Status', default: 'active' },
            region123: { label: 'Region', default: 'US' },
            USER_ID: { label: 'User ID', default: '1' },
            user_name: { label: 'User Name', default: 'John' },
            _private: { label: 'Private', default: 'value' },
            'user-id': { label: 'User ID', default: '123' },
            'region-code': { label: 'Region Code', default: 'US' },
        });
        expect(result.isInvalid).toBe(false);
        expect(result.invalidParameters).toEqual([]);
    });
    it('should reject invalid parameter names (dots, spaces, special chars)', () => {
        const result = (0, parameters_1.validateParameterNames)({
            'user.name': { label: 'User Name', default: 'John' },
            'user name': { label: 'User Name', default: 'John' },
            'user@email': { label: 'User Email', default: 'test' },
            price$amount: { label: 'Price Amount', default: '100' },
            'value#hash': { label: 'Value Hash', default: 'abc' },
            '': { label: 'Empty', default: 'value' },
        });
        expect(result.isInvalid).toBe(true);
        expect(result.invalidParameters).toContain('user.name');
        expect(result.invalidParameters).toContain('user name');
        expect(result.invalidParameters).toContain('user@email');
        expect(result.invalidParameters).toContain('price$amount');
        expect(result.invalidParameters).toContain('value#hash');
        expect(result.invalidParameters).toContain('');
    });
    it('should handle edge cases (undefined, empty)', () => {
        expect((0, parameters_1.validateParameterNames)(undefined).isInvalid).toBe(false);
        expect((0, parameters_1.validateParameterNames)(undefined).invalidParameters).toEqual([]);
        expect((0, parameters_1.validateParameterNames)({}).isInvalid).toBe(false);
        expect((0, parameters_1.validateParameterNames)({}).invalidParameters).toEqual([]);
    });
    it('should identify mixed valid and invalid parameters', () => {
        const result = (0, parameters_1.validateParameterNames)({
            validParam1: { label: 'Valid Param 1', default: 'value1' },
            'invalid.param': { label: 'Invalid Param', default: 'value2' },
            valid_param2: { label: 'Valid Param 2', default: 'value3' },
            'another invalid': { label: 'Another Invalid', default: 'value4' },
            'valid-param3': { label: 'Valid Param 3', default: 'value5' },
        });
        expect(result.isInvalid).toBe(true);
        expect(result.invalidParameters).toHaveLength(2);
        expect(result.invalidParameters).toContain('invalid.param');
        expect(result.invalidParameters).toContain('another invalid');
    });
    it('should not fail on reserved parameter names (user param wins, surfaced separately)', () => {
        const result = (0, parameters_1.validateParameterNames)({
            date_zoom: { label: 'Date zoom', default: 'week' },
        });
        expect(result.isInvalid).toBe(false);
        expect(result.reservedParameters).toContain('date_zoom');
        expect(result.invalidParameters).toEqual([]);
    });
    it('should report invalid and reserved names separately (only invalid fails)', () => {
        const result = (0, parameters_1.validateParameterNames)({
            'bad.name': { label: 'Bad', default: 'x' },
            date_zoom: { label: 'Date zoom', default: 'week' },
            good_name: { label: 'Good', default: 'y' },
        });
        expect(result.isInvalid).toBe(true);
        expect(result.invalidParameters).toEqual(['bad.name']);
        expect(result.reservedParameters).toEqual(['date_zoom']);
    });
});
describe('hasReservedParameterReference', () => {
    it('returns true when a reserved parameter is referenced', () => {
        expect((0, parameters_1.hasReservedParameterReference)(['region', 'date_zoom'])).toBe(true);
    });
    it('returns false when only user parameters are referenced', () => {
        expect((0, parameters_1.hasReservedParameterReference)(['region', 'status'])).toBe(false);
    });
    it('returns false for an empty list', () => {
        expect((0, parameters_1.hasReservedParameterReference)([])).toBe(false);
    });
});
describe('getMissingRequiredParameters', () => {
    it('flags a referenced parameter with no value and no default', () => {
        const result = (0, parameters_1.getMissingRequiredParameters)(['region'], {}, { region: { label: 'Region' } });
        expect(result).toEqual(['region']);
    });
    it('does not flag a parameter that has a value', () => {
        const result = (0, parameters_1.getMissingRequiredParameters)(['region'], { region: 'EU' }, { region: { label: 'Region' } });
        expect(result).toEqual([]);
    });
    it('does not flag a parameter explicitly set to a falsy value', () => {
        // Key presence, not truthiness: a user can deliberately pick 0, '' or [].
        const result = (0, parameters_1.getMissingRequiredParameters)(['amount', 'label', 'tags'], { amount: 0, label: '', tags: [] }, {
            amount: { label: 'Amount' },
            label: { label: 'Label' },
            tags: { label: 'Tags' },
        });
        expect(result).toEqual([]);
    });
    it('does not flag a parameter that has a default', () => {
        const result = (0, parameters_1.getMissingRequiredParameters)(['region'], {}, { region: { label: 'Region', default: 'EU' } });
        expect(result).toEqual([]);
    });
    it('does not flag a parameter whose default is a falsy value (0 / empty string)', () => {
        // A declared default counts as supplied (backend uses `default !== undefined`).
        const result = (0, parameters_1.getMissingRequiredParameters)(['count', 'note'], {}, {
            count: { label: 'Count', type: 'number', default: 0 },
            note: { label: 'Note', default: '' },
        });
        expect(result).toEqual([]);
    });
    it('still flags a parameter declared with no default key', () => {
        const result = (0, parameters_1.getMissingRequiredParameters)(['region'], {}, { region: { label: 'Region' } });
        expect(result).toEqual(['region']);
    });
    it('never flags a reserved parameter as missing', () => {
        // Reserved parameters are resolved server-side, so never user-required.
        const result = (0, parameters_1.getMissingRequiredParameters)(['date_zoom'], {}, {});
        expect(result).toEqual([]);
    });
});
describe('validateParameterConfiguration', () => {
    it('should pass for undefined parameters', () => {
        const result = (0, parameters_1.validateParameterConfiguration)(undefined);
        expect(result.isValid).toBe(true);
        expect(result.error).toBeNull();
    });
    it('should pass for empty parameters object', () => {
        const result = (0, parameters_1.validateParameterConfiguration)({});
        expect(result.isValid).toBe(true);
        expect(result.error).toBeNull();
    });
    it('should pass for parameter with options', () => {
        const result = (0, parameters_1.validateParameterConfiguration)({
            status: {
                label: 'Status',
                default: 'active',
                options: ['active', 'inactive'],
            },
        });
        expect(result.isValid).toBe(true);
        expect(result.error).toBeNull();
    });
    it('should pass for parameter with allow_custom_values: true', () => {
        const result = (0, parameters_1.validateParameterConfiguration)({
            region: {
                label: 'Region',
                default: 'US',
                allow_custom_values: true,
            },
        });
        expect(result.isValid).toBe(true);
        expect(result.error).toBeNull();
    });
    it('should pass for parameter with options_from_dimension', () => {
        const result = (0, parameters_1.validateParameterConfiguration)({
            customer: {
                label: 'Customer',
                default: 'Acme',
                options_from_dimension: {
                    model: 'customers',
                    dimension: 'name',
                },
            },
        });
        expect(result.isValid).toBe(true);
        expect(result.error).toBeNull();
    });
    it('should fail for parameter missing options, options_from_dimension, and allow_custom_values', () => {
        const result = (0, parameters_1.validateParameterConfiguration)({
            top_n: {
                label: 'Top N',
                description: 'Limit results to the top N records',
                default: 10,
                type: 'number',
            },
        });
        expect(result.isValid).toBe(false);
        expect(result.error).not.toBeNull();
    });
    it('should fail for parameter with allow_custom_values: false and no options', () => {
        const result = (0, parameters_1.validateParameterConfiguration)({
            region: {
                label: 'Region',
                default: 'US',
                allow_custom_values: false,
            },
        });
        expect(result.isValid).toBe(false);
        expect(result.error).not.toBeNull();
    });
    it('should pass when mixing valid parameter configurations', () => {
        const result = (0, parameters_1.validateParameterConfiguration)({
            status: {
                label: 'Status',
                options: ['active', 'inactive'],
                default: 'active',
            },
            region: {
                label: 'Region',
                allow_custom_values: true,
                default: 'US',
            },
            customer: {
                label: 'Customer',
                options_from_dimension: {
                    model: 'customers',
                    dimension: 'name',
                },
            },
        });
        expect(result.isValid).toBe(true);
        expect(result.error).toBeNull();
    });
});
describe('getExploreParameterDefinitions', () => {
    it('includes active explore parameter aliases so shadowed reserved names can be matched by reference', () => {
        const definitions = (0, parameters_1.getExploreParameterDefinitions)({
            name: 'orders',
            label: 'Orders',
            baseTable: 'orders',
            joinedTables: [],
            type: explore_1.ExploreType.DEFAULT,
            tables: {
                orders: {
                    name: 'orders',
                    label: 'Orders',
                    parameters: {
                        date_zoom: {
                            label: 'Table-scoped date zoom',
                            options: ['week'],
                        },
                    },
                    dimensions: {},
                    metrics: {},
                    lineageGraph: {},
                },
            },
            parameters: {
                date_zoom: {
                    label: 'Shadowed date zoom',
                    options: ['week'],
                },
            },
        });
        expect(definitions['orders.date_zoom']?.label).toBe('Table-scoped date zoom');
        expect(definitions.date_zoom?.label).toBe('Shadowed date zoom');
    });
});
describe('getReferencedParameterDefinitions', () => {
    const region = { label: 'Region' };
    const shadowedDateZoom = {
        label: 'Shadowed date zoom',
    };
    it('returns only definitions that are referenced', () => {
        expect((0, parameters_1.getReferencedParameterDefinitions)({ region }, ['region'])).toEqual({ region });
    });
    it('excludes references that have no user definition (e.g. a reserved-only reference)', () => {
        // date_zoom is referenced but not user-defined: it is reserved-only and must not show.
        expect((0, parameters_1.getReferencedParameterDefinitions)({ region }, ['date_zoom'])).toEqual({});
    });
    it('includes a user-defined parameter that shadows a reserved name', () => {
        expect((0, parameters_1.getReferencedParameterDefinitions)({ date_zoom: shadowedDateZoom }, [
            'date_zoom',
        ])).toEqual({ date_zoom: shadowedDateZoom });
    });
    it('returns nothing when there are no references', () => {
        expect((0, parameters_1.getReferencedParameterDefinitions)({ region }, undefined)).toEqual({});
        expect((0, parameters_1.getReferencedParameterDefinitions)({ region }, [])).toEqual({});
    });
});
//# sourceMappingURL=parameters.test.js.map