"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const github = __importStar(require("@actions/github"));
const ava_1 = __importDefault(require("ava"));
const semver_1 = require("semver");
const sinon = __importStar(require("sinon"));
const api = __importStar(require("./api-client"));
const codeql_1 = require("./codeql");
const configUtils = __importStar(require("./config-utils"));
const feature_flags_1 = require("./feature-flags");
const languages_1 = require("./languages");
const logging_1 = require("./logging");
const testing_utils_1 = require("./testing-utils");
const util = __importStar(require("./util"));
(0, testing_utils_1.setupTests)(ava_1.default);
const sampleApiDetails = {
    auth: "token",
    externalRepoAuth: "token",
    url: "https://github.example.com",
};
const gitHubVersion = { type: util.GitHubVariant.DOTCOM };
// Returns the filepath of the newly-created file
function createConfigFile(inputFileContents, tmpDir) {
    const configFilePath = path.join(tmpDir, "input");
    fs.writeFileSync(configFilePath, inputFileContents, "utf8");
    return configFilePath;
}
function mockGetContents(content) {
    // Passing an auth token is required, so we just use a dummy value
    const client = github.getOctokit("123");
    const response = {
        data: content,
    };
    const spyGetContents = sinon
        .stub(client.repos, "getContent")
        .resolves(response);
    sinon.stub(api, "getApiClient").value(() => client);
    return spyGetContents;
}
function mockListLanguages(languages) {
    // Passing an auth token is required, so we just use a dummy value
    const client = github.getOctokit("123");
    const response = {
        data: {},
    };
    for (const language of languages) {
        response.data[language] = 123;
    }
    sinon.stub(client.repos, "listLanguages").resolves(response);
    sinon.stub(api, "getApiClient").value(() => client);
}
(0, ava_1.default)("load empty config", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const logger = (0, logging_1.getRunnerLogger)(true);
        const languages = "javascript,python";
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {
                        javascript: { queries: ["query1.ql"] },
                        python: { queries: ["query2.ql"] },
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        const config = await configUtils.initConfig(languages, undefined, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), logger);
        t.deepEqual(config, await configUtils.getDefaultConfig(languages, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), logger));
    });
});
(0, ava_1.default)("loading config saves config", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const logger = (0, logging_1.getRunnerLogger)(true);
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {
                        javascript: { queries: ["query1.ql"] },
                        python: { queries: ["query2.ql"] },
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        // Sanity check the saved config file does not already exist
        t.false(fs.existsSync(configUtils.getPathToParsedConfigFile(tmpDir)));
        // Sanity check that getConfig returns undefined before we have called initConfig
        t.deepEqual(await configUtils.getConfig(tmpDir, logger), undefined);
        const config1 = await configUtils.initConfig("javascript,python", undefined, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), logger);
        // The saved config file should now exist
        t.true(fs.existsSync(configUtils.getPathToParsedConfigFile(tmpDir)));
        // And that same newly-initialised config should now be returned by getConfig
        const config2 = await configUtils.getConfig(tmpDir, logger);
        t.deepEqual(config1, config2);
    });
});
(0, ava_1.default)("load input outside of workspace", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        try {
            await configUtils.initConfig(undefined, undefined, undefined, "../input", undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, (0, codeql_1.getCachedCodeQL)(), tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            throw new Error("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getConfigFileOutsideWorkspaceErrorMessage(path.join(tmpDir, "../input"))));
        }
    });
});
(0, ava_1.default)("load non-local input with invalid repo syntax", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        // no filename given, just a repo
        const configFile = "octo-org/codeql-config@main";
        try {
            await configUtils.initConfig(undefined, undefined, undefined, configFile, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, (0, codeql_1.getCachedCodeQL)(), tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            throw new Error("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getConfigFileRepoFormatInvalidMessage("octo-org/codeql-config@main")));
        }
    });
});
(0, ava_1.default)("load non-existent input", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const languages = "javascript";
        const configFile = "input";
        t.false(fs.existsSync(path.join(tmpDir, configFile)));
        try {
            await configUtils.initConfig(languages, undefined, undefined, configFile, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, (0, codeql_1.getCachedCodeQL)(), tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            throw new Error("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getConfigFileDoesNotExistErrorMessage(path.join(tmpDir, "input"))));
        }
    });
});
(0, ava_1.default)("load non-empty input", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {
                        javascript: {
                            "/foo/a.ql": {},
                            "/bar/b.ql": {},
                        },
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        // Just create a generic config object with non-default values for all fields
        const inputFileContents = `
      name: my config
      disable-default-queries: true
      queries:
        - uses: ./foo
      paths-ignore:
        - a
        - b
      paths:
        - c/d`;
        fs.mkdirSync(path.join(tmpDir, "foo"));
        // And the config we expect it to parse to
        const expectedConfig = {
            languages: [languages_1.Language.javascript],
            queries: {
                javascript: {
                    builtin: [],
                    custom: [
                        {
                            queries: ["/foo/a.ql", "/bar/b.ql"],
                            searchPath: tmpDir,
                        },
                    ],
                },
            },
            pathsIgnore: ["a", "b"],
            paths: ["c/d"],
            originalUserInput: {
                name: "my config",
                "disable-default-queries": true,
                queries: [{ uses: "./foo" }],
                "paths-ignore": ["a", "b"],
                paths: ["c/d"],
            },
            tempDir: tmpDir,
            toolCacheDir: tmpDir,
            codeQLCmd: codeQL.getPath(),
            gitHubVersion,
            dbLocation: path.resolve(tmpDir, "codeql_databases"),
            packs: {},
            debugMode: false,
        };
        const languages = "javascript";
        const configFilePath = createConfigFile(inputFileContents, tmpDir);
        const actualConfig = await configUtils.initConfig(languages, undefined, undefined, configFilePath, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        // Should exactly equal the object we constructed earlier
        t.deepEqual(actualConfig, expectedConfig);
    });
});
(0, ava_1.default)("Default queries are used", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        // Check that the default behaviour is to add the default queries.
        // In this case if a config file is specified but does not include
        // the disable-default-queries field.
        // We determine this by whether CodeQL.resolveQueries is called
        // with the correct arguments.
        const resolveQueriesArgs = [];
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries(queries, extraSearchPath) {
                resolveQueriesArgs.push({ queries, extraSearchPath });
                return {
                    byLanguage: {
                        javascript: {
                            "foo.ql": {},
                        },
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        // The important point of this config is that it doesn't specify
        // the disable-default-queries field.
        // Any other details are hopefully irrelevant for this test.
        const inputFileContents = `
      paths:
        - foo`;
        fs.mkdirSync(path.join(tmpDir, "foo"));
        const languages = "javascript";
        const configFilePath = createConfigFile(inputFileContents, tmpDir);
        await configUtils.initConfig(languages, undefined, undefined, configFilePath, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        // Check resolve queries was called correctly
        t.deepEqual(resolveQueriesArgs.length, 1);
        t.deepEqual(resolveQueriesArgs[0].queries, [
            "javascript-code-scanning.qls",
        ]);
        t.deepEqual(resolveQueriesArgs[0].extraSearchPath, undefined);
    });
});
/**
 * Returns the provided queries, just in the right format for a resolved query
 * This way we can test by seeing which returned items are in the final
 * configuration.
 */
function queriesToResolvedQueryForm(queries) {
    const dummyResolvedQueries = {};
    for (const q of queries) {
        dummyResolvedQueries[q] = {};
    }
    return {
        byLanguage: {
            javascript: dummyResolvedQueries,
        },
        noDeclaredLanguage: {},
        multipleDeclaredLanguages: {},
    };
}
(0, ava_1.default)("Queries can be specified in config file", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const inputFileContents = `
      name: my config
      queries:
        - uses: ./foo`;
        const configFilePath = createConfigFile(inputFileContents, tmpDir);
        fs.mkdirSync(path.join(tmpDir, "foo"));
        const resolveQueriesArgs = [];
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries(queries, extraSearchPath) {
                resolveQueriesArgs.push({ queries, extraSearchPath });
                return queriesToResolvedQueryForm(queries);
            },
        });
        const languages = "javascript";
        const config = await configUtils.initConfig(languages, undefined, undefined, configFilePath, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        // Check resolveQueries was called correctly
        // It'll be called once for the default queries
        // and once for `./foo` from the config file.
        t.deepEqual(resolveQueriesArgs.length, 2);
        t.deepEqual(resolveQueriesArgs[1].queries.length, 1);
        t.regex(resolveQueriesArgs[1].queries[0], /.*\/foo$/);
        // Now check that the end result contains the default queries and the query from config
        t.deepEqual(config.queries["javascript"].builtin.length, 1);
        t.deepEqual(config.queries["javascript"].custom.length, 1);
        t.regex(config.queries["javascript"].builtin[0], /javascript-code-scanning.qls$/);
        t.regex(config.queries["javascript"].custom[0].queries[0], /.*\/foo$/);
    });
});
(0, ava_1.default)("Queries from config file can be overridden in workflow file", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const inputFileContents = `
      name: my config
      queries:
        - uses: ./foo`;
        const configFilePath = createConfigFile(inputFileContents, tmpDir);
        // This config item should take precedence over the config file but shouldn't affect the default queries.
        const testQueries = "./override";
        fs.mkdirSync(path.join(tmpDir, "foo"));
        fs.mkdirSync(path.join(tmpDir, "override"));
        const resolveQueriesArgs = [];
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries(queries, extraSearchPath) {
                resolveQueriesArgs.push({ queries, extraSearchPath });
                return queriesToResolvedQueryForm(queries);
            },
        });
        const languages = "javascript";
        const config = await configUtils.initConfig(languages, testQueries, undefined, configFilePath, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        // Check resolveQueries was called correctly
        // It'll be called once for the default queries and once for `./override`,
        // but won't be called for './foo' from the config file.
        t.deepEqual(resolveQueriesArgs.length, 2);
        t.deepEqual(resolveQueriesArgs[1].queries.length, 1);
        t.regex(resolveQueriesArgs[1].queries[0], /.*\/override$/);
        // Now check that the end result contains only the default queries and the override query
        t.deepEqual(config.queries["javascript"].builtin.length, 1);
        t.deepEqual(config.queries["javascript"].custom.length, 1);
        t.regex(config.queries["javascript"].builtin[0], /javascript-code-scanning.qls$/);
        t.regex(config.queries["javascript"].custom[0].queries[0], /.*\/override$/);
    });
});
(0, ava_1.default)("Queries in workflow file can be used in tandem with the 'disable default queries' option", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        process.env["RUNNER_TEMP"] = tmpDir;
        process.env["GITHUB_WORKSPACE"] = tmpDir;
        const inputFileContents = `
      name: my config
      disable-default-queries: true`;
        const configFilePath = createConfigFile(inputFileContents, tmpDir);
        const testQueries = "./workflow-query";
        fs.mkdirSync(path.join(tmpDir, "workflow-query"));
        const resolveQueriesArgs = [];
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries(queries, extraSearchPath) {
                resolveQueriesArgs.push({ queries, extraSearchPath });
                return queriesToResolvedQueryForm(queries);
            },
        });
        const languages = "javascript";
        const config = await configUtils.initConfig(languages, testQueries, undefined, configFilePath, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        // Check resolveQueries was called correctly
        // It'll be called once for `./workflow-query`,
        // but won't be called for the default one since that was disabled
        t.deepEqual(resolveQueriesArgs.length, 1);
        t.deepEqual(resolveQueriesArgs[0].queries.length, 1);
        t.regex(resolveQueriesArgs[0].queries[0], /.*\/workflow-query$/);
        // Now check that the end result contains only the workflow query, and not the default one
        t.deepEqual(config.queries["javascript"].builtin.length, 0);
        t.deepEqual(config.queries["javascript"].custom.length, 1);
        t.regex(config.queries["javascript"].custom[0].queries[0], /.*\/workflow-query$/);
    });
});
(0, ava_1.default)("Multiple queries can be specified in workflow file, no config file required", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        fs.mkdirSync(path.join(tmpDir, "override1"));
        fs.mkdirSync(path.join(tmpDir, "override2"));
        const testQueries = "./override1,./override2";
        const resolveQueriesArgs = [];
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries(queries, extraSearchPath) {
                resolveQueriesArgs.push({ queries, extraSearchPath });
                return queriesToResolvedQueryForm(queries);
            },
        });
        const languages = "javascript";
        const config = await configUtils.initConfig(languages, testQueries, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        // Check resolveQueries was called correctly:
        // It'll be called once for the default queries,
        // and then once for each of the two queries from the workflow
        t.deepEqual(resolveQueriesArgs.length, 3);
        t.deepEqual(resolveQueriesArgs[1].queries.length, 1);
        t.deepEqual(resolveQueriesArgs[2].queries.length, 1);
        t.regex(resolveQueriesArgs[1].queries[0], /.*\/override1$/);
        t.regex(resolveQueriesArgs[2].queries[0], /.*\/override2$/);
        // Now check that the end result contains both the queries from the workflow, as well as the defaults
        t.deepEqual(config.queries["javascript"].builtin.length, 1);
        t.deepEqual(config.queries["javascript"].custom.length, 2);
        t.regex(config.queries["javascript"].builtin[0], /javascript-code-scanning.qls$/);
        t.regex(config.queries["javascript"].custom[0].queries[0], /.*\/override1$/);
        t.regex(config.queries["javascript"].custom[1].queries[0], /.*\/override2$/);
    });
});
(0, ava_1.default)("Queries in workflow file can be added to the set of queries without overriding config file", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        process.env["RUNNER_TEMP"] = tmpDir;
        process.env["GITHUB_WORKSPACE"] = tmpDir;
        const inputFileContents = `
      name: my config
      queries:
        - uses: ./foo`;
        const configFilePath = createConfigFile(inputFileContents, tmpDir);
        // These queries shouldn't override anything, because the value is prefixed with "+"
        const testQueries = "+./additional1,./additional2";
        fs.mkdirSync(path.join(tmpDir, "foo"));
        fs.mkdirSync(path.join(tmpDir, "additional1"));
        fs.mkdirSync(path.join(tmpDir, "additional2"));
        const resolveQueriesArgs = [];
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries(queries, extraSearchPath) {
                resolveQueriesArgs.push({ queries, extraSearchPath });
                return queriesToResolvedQueryForm(queries);
            },
        });
        const languages = "javascript";
        const config = await configUtils.initConfig(languages, testQueries, undefined, configFilePath, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        // Check resolveQueries was called correctly
        // It'll be called once for the default queries,
        // once for each of additional1 and additional2,
        // and once for './foo' from the config file
        t.deepEqual(resolveQueriesArgs.length, 4);
        t.deepEqual(resolveQueriesArgs[1].queries.length, 1);
        t.regex(resolveQueriesArgs[1].queries[0], /.*\/additional1$/);
        t.deepEqual(resolveQueriesArgs[2].queries.length, 1);
        t.regex(resolveQueriesArgs[2].queries[0], /.*\/additional2$/);
        t.deepEqual(resolveQueriesArgs[3].queries.length, 1);
        t.regex(resolveQueriesArgs[3].queries[0], /.*\/foo$/);
        // Now check that the end result contains all the queries
        t.deepEqual(config.queries["javascript"].builtin.length, 1);
        t.deepEqual(config.queries["javascript"].custom.length, 3);
        t.regex(config.queries["javascript"].builtin[0], /javascript-code-scanning.qls$/);
        t.regex(config.queries["javascript"].custom[0].queries[0], /.*\/additional1$/);
        t.regex(config.queries["javascript"].custom[1].queries[0], /.*\/additional2$/);
        t.regex(config.queries["javascript"].custom[2].queries[0], /.*\/foo$/);
    });
});
(0, ava_1.default)("Invalid queries in workflow file handled correctly", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const queries = "foo/bar@v1@v3";
        const languages = "javascript";
        // This function just needs to be type-correct; it doesn't need to do anything,
        // since we're deliberately passing in invalid data
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {
                        javascript: {},
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        try {
            await configUtils.initConfig(languages, queries, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            t.fail("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getQueryUsesInvalid(undefined, "foo/bar@v1@v3")));
        }
    });
});
(0, ava_1.default)("API client used when reading remote config", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {
                        javascript: {
                            "foo.ql": {},
                        },
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        const inputFileContents = `
      name: my config
      disable-default-queries: true
      queries:
        - uses: ./
        - uses: ./foo
        - uses: foo/bar@dev
      paths-ignore:
        - a
        - b
      paths:
        - c/d`;
        const dummyResponse = {
            content: Buffer.from(inputFileContents).toString("base64"),
        };
        const spyGetContents = mockGetContents(dummyResponse);
        // Create checkout directory for remote queries repository
        fs.mkdirSync(path.join(tmpDir, "foo/bar/dev"), { recursive: true });
        const configFile = "octo-org/codeql-config/config.yaml@main";
        const languages = "javascript";
        await configUtils.initConfig(languages, undefined, undefined, configFile, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        t.assert(spyGetContents.called);
    });
});
(0, ava_1.default)("Remote config handles the case where a directory is provided", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const dummyResponse = []; // directories are returned as arrays
        mockGetContents(dummyResponse);
        const repoReference = "octo-org/codeql-config/config.yaml@main";
        try {
            await configUtils.initConfig(undefined, undefined, undefined, repoReference, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, (0, codeql_1.getCachedCodeQL)(), tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            throw new Error("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getConfigFileDirectoryGivenMessage(repoReference)));
        }
    });
});
(0, ava_1.default)("Invalid format of remote config handled correctly", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const dummyResponse = {
        // note no "content" property here
        };
        mockGetContents(dummyResponse);
        const repoReference = "octo-org/codeql-config/config.yaml@main";
        try {
            await configUtils.initConfig(undefined, undefined, undefined, repoReference, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, (0, codeql_1.getCachedCodeQL)(), tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            throw new Error("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getConfigFileFormatInvalidMessage(repoReference)));
        }
    });
});
(0, ava_1.default)("No detected languages", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        mockListLanguages([]);
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveLanguages() {
                return {};
            },
        });
        try {
            await configUtils.initConfig(undefined, undefined, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            throw new Error("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getNoLanguagesError()));
        }
    });
});
(0, ava_1.default)("Unknown languages", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const languages = "rubbish,english";
        try {
            await configUtils.initConfig(languages, undefined, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, (0, codeql_1.getCachedCodeQL)(), tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
            throw new Error("initConfig did not throw error");
        }
        catch (err) {
            t.deepEqual(err, new Error(configUtils.getUnknownLanguagesError(["rubbish", "english"])));
        }
    });
});
(0, ava_1.default)("Config specifies packages", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {},
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        const inputFileContents = `
      name: my config
      disable-default-queries: true
      packs:
        - a/b@1.2.3
      `;
        const configFile = path.join(tmpDir, "codeql-config.yaml");
        fs.writeFileSync(configFile, inputFileContents);
        const languages = "javascript";
        const { packs } = await configUtils.initConfig(languages, undefined, undefined, configFile, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        t.deepEqual(packs, {
            [languages_1.Language.javascript]: [
                {
                    packName: "a/b",
                    version: (0, semver_1.clean)("1.2.3"),
                },
            ],
        });
    });
});
(0, ava_1.default)("Config specifies packages for multiple languages", async (t) => {
    return await util.withTmpDir(async (tmpDir) => {
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {
                        cpp: { "/foo/a.ql": {} },
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        const inputFileContents = `
      name: my config
      disable-default-queries: true
      queries:
      - uses: ./foo
      packs:
        javascript:
          - a/b@1.2.3
        python:
          - c/d@1.2.3
      `;
        const configFile = path.join(tmpDir, "codeql-config.yaml");
        fs.writeFileSync(configFile, inputFileContents);
        fs.mkdirSync(path.join(tmpDir, "foo"));
        const languages = "javascript,python,cpp";
        const { packs, queries } = await configUtils.initConfig(languages, undefined, undefined, configFile, undefined, false, { owner: "github", repo: "example" }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
        t.deepEqual(packs, {
            [languages_1.Language.javascript]: [
                {
                    packName: "a/b",
                    version: (0, semver_1.clean)("1.2.3"),
                },
            ],
            [languages_1.Language.python]: [
                {
                    packName: "c/d",
                    version: (0, semver_1.clean)("1.2.3"),
                },
            ],
        });
        t.deepEqual(queries, {
            cpp: {
                builtin: [],
                custom: [
                    {
                        queries: ["/foo/a.ql"],
                        searchPath: tmpDir,
                    },
                ],
            },
            javascript: {
                builtin: [],
                custom: [],
            },
            python: {
                builtin: [],
                custom: [],
            },
        });
    });
});
function doInvalidInputTest(testName, inputFileContents, expectedErrorMessageGenerator) {
    (0, ava_1.default)(`load invalid input - ${testName}`, async (t) => {
        return await util.withTmpDir(async (tmpDir) => {
            const codeQL = (0, codeql_1.setCodeQL)({
                async resolveQueries() {
                    return {
                        byLanguage: {},
                        noDeclaredLanguage: {},
                        multipleDeclaredLanguages: {},
                    };
                },
            });
            const languages = "javascript";
            const configFile = "input";
            const inputFile = path.join(tmpDir, configFile);
            fs.writeFileSync(inputFile, inputFileContents, "utf8");
            try {
                await configUtils.initConfig(languages, undefined, undefined, configFile, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)([]), (0, logging_1.getRunnerLogger)(true));
                throw new Error("initConfig did not throw error");
            }
            catch (err) {
                t.deepEqual(err, new Error(expectedErrorMessageGenerator(inputFile)));
            }
        });
    });
}
doInvalidInputTest("name invalid type", `
  name:
    - foo: bar`, configUtils.getNameInvalid);
doInvalidInputTest("disable-default-queries invalid type", `disable-default-queries: 42`, configUtils.getDisableDefaultQueriesInvalid);
doInvalidInputTest("queries invalid type", `queries: foo`, configUtils.getQueriesInvalid);
doInvalidInputTest("paths-ignore invalid type", `paths-ignore: bar`, configUtils.getPathsIgnoreInvalid);
doInvalidInputTest("paths invalid type", `paths: 17`, configUtils.getPathsInvalid);
doInvalidInputTest("queries uses invalid type", `
  queries:
  - uses:
      - hello: world`, configUtils.getQueryUsesInvalid);
function doInvalidQueryUsesTest(input, expectedErrorMessageGenerator) {
    // Invalid contents of a "queries.uses" field.
    // Should fail with the expected error message
    const inputFileContents = `
    name: my config
    queries:
      - name: foo
        uses: ${input}`;
    doInvalidInputTest(`queries uses "${input}"`, inputFileContents, expectedErrorMessageGenerator);
}
// Various "uses" fields, and the errors they should produce
doInvalidQueryUsesTest("''", (c) => configUtils.getQueryUsesInvalid(c, undefined));
doInvalidQueryUsesTest("foo/bar", (c) => configUtils.getQueryUsesInvalid(c, "foo/bar"));
doInvalidQueryUsesTest("foo/bar@v1@v2", (c) => configUtils.getQueryUsesInvalid(c, "foo/bar@v1@v2"));
doInvalidQueryUsesTest("foo@master", (c) => configUtils.getQueryUsesInvalid(c, "foo@master"));
doInvalidQueryUsesTest("https://github.com/foo/bar@master", (c) => configUtils.getQueryUsesInvalid(c, "https://github.com/foo/bar@master"));
doInvalidQueryUsesTest("./foo", (c) => configUtils.getLocalPathDoesNotExist(c, "foo"));
doInvalidQueryUsesTest("./..", (c) => configUtils.getLocalPathOutsideOfRepository(c, ".."));
const validPaths = [
    "foo",
    "foo/",
    "foo/**",
    "foo/**/",
    "foo/**/**",
    "foo/**/bar/**/baz",
    "**/",
    "**/foo",
    "/foo",
];
const invalidPaths = ["a/***/b", "a/**b", "a/b**", "**"];
(0, ava_1.default)("path validations", (t) => {
    // Dummy values to pass to validateAndSanitisePath
    const propertyName = "paths";
    const configFile = "./.github/codeql/config.yml";
    for (const validPath of validPaths) {
        t.truthy(configUtils.validateAndSanitisePath(validPath, propertyName, configFile, (0, logging_1.getRunnerLogger)(true)));
    }
    for (const invalidPath of invalidPaths) {
        t.throws(() => configUtils.validateAndSanitisePath(invalidPath, propertyName, configFile, (0, logging_1.getRunnerLogger)(true)));
    }
});
(0, ava_1.default)("path sanitisation", (t) => {
    // Dummy values to pass to validateAndSanitisePath
    const propertyName = "paths";
    const configFile = "./.github/codeql/config.yml";
    // Valid paths are not modified
    t.deepEqual(configUtils.validateAndSanitisePath("foo/bar", propertyName, configFile, (0, logging_1.getRunnerLogger)(true)), "foo/bar");
    // Trailing stars are stripped
    t.deepEqual(configUtils.validateAndSanitisePath("foo/**", propertyName, configFile, (0, logging_1.getRunnerLogger)(true)), "foo/");
});
/**
 * Test macro for ensuring the packs block is valid
 */
function parsePacksMacro(t, packsByLanguage, languages, expected) {
    t.deepEqual(configUtils.parsePacksFromConfig(packsByLanguage, languages, "/a/b"), expected);
}
parsePacksMacro.title = (providedTitle) => `Parse Packs: ${providedTitle}`;
/**
 * Test macro for testing when the packs block is invalid
 */
function parsePacksErrorMacro(t, packsByLanguage, languages, expected) {
    t.throws(() => {
        configUtils.parsePacksFromConfig(packsByLanguage, languages, "/a/b");
    }, {
        message: expected,
    });
}
parsePacksErrorMacro.title = (providedTitle) => `Parse Packs Error: ${providedTitle}`;
/**
 * Test macro for testing when the packs block is invalid
 */
function invalidPackNameMacro(t, name) {
    parsePacksErrorMacro(t, { [languages_1.Language.cpp]: [name] }, [languages_1.Language.cpp], new RegExp(`The configuration file "/a/b" is invalid: property "packs" "${name}" is not a valid pack`));
}
invalidPackNameMacro.title = (_, arg) => `Invalid pack string: ${arg}`;
(0, ava_1.default)("no packs", parsePacksMacro, {}, [], {});
(0, ava_1.default)("two packs", parsePacksMacro, ["a/b", "c/d@1.2.3"], [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [
        { packName: "a/b", version: undefined },
        { packName: "c/d", version: (0, semver_1.clean)("1.2.3") },
    ],
});
(0, ava_1.default)("two packs with spaces", parsePacksMacro, [" a/b ", " c/d@1.2.3 "], [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [
        { packName: "a/b", version: undefined },
        { packName: "c/d", version: (0, semver_1.clean)("1.2.3") },
    ],
});
(0, ava_1.default)("two packs with language", parsePacksMacro, {
    [languages_1.Language.cpp]: ["a/b", "c/d@1.2.3"],
    [languages_1.Language.java]: ["d/e", "f/g@1.2.3"],
}, [languages_1.Language.cpp, languages_1.Language.java, languages_1.Language.csharp], {
    [languages_1.Language.cpp]: [
        { packName: "a/b", version: undefined },
        { packName: "c/d", version: (0, semver_1.clean)("1.2.3") },
    ],
    [languages_1.Language.java]: [
        { packName: "d/e", version: undefined },
        { packName: "f/g", version: (0, semver_1.clean)("1.2.3") },
    ],
});
(0, ava_1.default)("no language", parsePacksErrorMacro, ["a/b@1.2.3"], [languages_1.Language.java, languages_1.Language.python], /The configuration file "\/a\/b" is invalid: property "packs" must split packages by language/);
(0, ava_1.default)("invalid language", parsePacksErrorMacro, { [languages_1.Language.java]: ["c/d"] }, [languages_1.Language.cpp], /The configuration file "\/a\/b" is invalid: property "packs" has "java", but it is not one of the languages to analyze/);
(0, ava_1.default)("not an array", parsePacksErrorMacro, { [languages_1.Language.cpp]: "c/d" }, [languages_1.Language.cpp], /The configuration file "\/a\/b" is invalid: property "packs" must be an array of non-empty strings/);
(0, ava_1.default)(invalidPackNameMacro, "c"); // all packs require at least a scope and a name
(0, ava_1.default)(invalidPackNameMacro, "c-/d");
(0, ava_1.default)(invalidPackNameMacro, "-c/d");
(0, ava_1.default)(invalidPackNameMacro, "c/d_d");
(0, ava_1.default)(invalidPackNameMacro, "c/d@x");
/**
 * Test macro for testing the packs block and the packs input
 */
function parseInputAndConfigMacro(t, packsFromConfig, packsFromInput, languages, expected) {
    t.deepEqual(configUtils.parsePacks(packsFromConfig, packsFromInput, languages, "/a/b"), expected);
}
parseInputAndConfigMacro.title = (providedTitle) => `Parse Packs input and config: ${providedTitle}`;
function parseInputAndConfigErrorMacro(t, packsFromConfig, packsFromInput, languages, expected) {
    t.throws(() => {
        configUtils.parsePacks(packsFromConfig, packsFromInput, languages, "/a/b");
    }, {
        message: expected,
    });
}
parseInputAndConfigErrorMacro.title = (providedTitle) => `Parse Packs input and config Error: ${providedTitle}`;
(0, ava_1.default)("input only", parseInputAndConfigMacro, {}, " c/d ", [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [{ packName: "c/d", version: undefined }],
});
(0, ava_1.default)("input only with multiple", parseInputAndConfigMacro, {}, "a/b , c/d@1.2.3", [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [
        { packName: "a/b", version: undefined },
        { packName: "c/d", version: "1.2.3" },
    ],
});
(0, ava_1.default)("input only with +", parseInputAndConfigMacro, {}, "  +  a/b , c/d@1.2.3 ", [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [
        { packName: "a/b", version: undefined },
        { packName: "c/d", version: "1.2.3" },
    ],
});
(0, ava_1.default)("config only", parseInputAndConfigMacro, ["a/b", "c/d"], "  ", [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [
        { packName: "a/b", version: undefined },
        { packName: "c/d", version: undefined },
    ],
});
(0, ava_1.default)("input overrides", parseInputAndConfigMacro, ["a/b", "c/d"], " e/f, g/h@1.2.3 ", [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [
        { packName: "e/f", version: undefined },
        { packName: "g/h", version: "1.2.3" },
    ],
});
(0, ava_1.default)("input and config", parseInputAndConfigMacro, ["a/b", "c/d"], " +e/f, g/h@1.2.3 ", [languages_1.Language.cpp], {
    [languages_1.Language.cpp]: [
        { packName: "e/f", version: undefined },
        { packName: "g/h", version: "1.2.3" },
        { packName: "a/b", version: undefined },
        { packName: "c/d", version: undefined },
    ],
});
(0, ava_1.default)("input with no language", parseInputAndConfigErrorMacro, {}, "c/d", [], /No languages specified/);
(0, ava_1.default)("input with two languages", parseInputAndConfigErrorMacro, {}, "c/d", [languages_1.Language.cpp, languages_1.Language.csharp], /multi-language analysis/);
(0, ava_1.default)("input with + only", parseInputAndConfigErrorMacro, {}, " + ", [languages_1.Language.cpp], /remove the '\+'/);
(0, ava_1.default)("input with invalid pack name", parseInputAndConfigErrorMacro, {}, " xxx", [languages_1.Language.cpp], /"xxx" is not a valid pack/);
async function mlPoweredQueriesMacro(t, isMlPoweredQueriesFlagEnabled, queriesInput, shouldRunMlPoweredQueries) {
    return await util.withTmpDir(async (tmpDir) => {
        const codeQL = (0, codeql_1.setCodeQL)({
            async resolveQueries() {
                return {
                    byLanguage: {
                        javascript: { "fake-query.ql": {} },
                    },
                    noDeclaredLanguage: {},
                    multipleDeclaredLanguages: {},
                };
            },
        });
        const { packs } = await configUtils.initConfig("javascript", queriesInput, undefined, undefined, undefined, false, { owner: "github", repo: "example " }, tmpDir, tmpDir, codeQL, tmpDir, gitHubVersion, sampleApiDetails, (0, feature_flags_1.createFeatureFlags)(isMlPoweredQueriesFlagEnabled
            ? [feature_flags_1.FeatureFlag.MlPoweredQueriesEnabled]
            : []), (0, logging_1.getRunnerLogger)(true));
        if (shouldRunMlPoweredQueries) {
            t.deepEqual(packs, {
                [languages_1.Language.javascript]: [
                    {
                        packName: "codeql/javascript-experimental-atm-queries",
                        version: (0, semver_1.clean)("0.0.1"),
                    },
                ],
            });
        }
        else {
            t.deepEqual(packs, {});
        }
    });
}
mlPoweredQueriesMacro.title = (_providedTitle, isMlPoweredQueriesFlagEnabled, queriesInput, shouldRunMlPoweredQueries) => {
    const queriesInputDescription = queriesInput
        ? `'queries: ${queriesInput}'`
        : "default config";
    return `ML-powered queries ${shouldRunMlPoweredQueries ? "are" : "aren't"} loaded for ${queriesInputDescription} when feature flag is ${isMlPoweredQueriesFlagEnabled ? "enabled" : "disabled"}`;
};
// macro, isMlPoweredQueriesFlagEnabled, queriesInput, shouldRunMlPoweredQueries
(0, ava_1.default)(mlPoweredQueriesMacro, false, "security-extended", false);
(0, ava_1.default)(mlPoweredQueriesMacro, true, undefined, false);
(0, ava_1.default)(mlPoweredQueriesMacro, true, "security-extended", true);
(0, ava_1.default)(mlPoweredQueriesMacro, true, "security-and-quality", true);
//# sourceMappingURL=config-utils.test.js.map