mirror of
https://github.com/github/codeql-action.git
synced 2025-12-30 11:10:22 +08:00
318 lines
9.1 KiB
TypeScript
318 lines
9.1 KiB
TypeScript
import * as octokit from '@octokit/rest';
|
|
import test from 'ava';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import sinon from 'sinon';
|
|
|
|
import * as configUtils from './config-utils';
|
|
import {silenceDebugOutput} from './testing-utils';
|
|
import * as util from './util';
|
|
|
|
silenceDebugOutput(test);
|
|
|
|
function setInput(name: string, value: string | undefined) {
|
|
// Transformation copied from
|
|
// https://github.com/actions/toolkit/blob/05e39f551d33e1688f61b209ab5cdd335198f1b8/packages/core/src/core.ts#L69
|
|
const envVar = `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;
|
|
if (value !== undefined) {
|
|
process.env[envVar] = value;
|
|
} else {
|
|
delete process.env[envVar];
|
|
}
|
|
}
|
|
|
|
test("load empty config", async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
setInput('config-file', undefined);
|
|
|
|
const config = await configUtils.loadConfig();
|
|
|
|
t.deepEqual(config, new configUtils.Config());
|
|
});
|
|
});
|
|
|
|
test("loading config saves config", async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
const configFile = configUtils.getConfigFile();
|
|
// Sanity check the saved config file does not already exist
|
|
t.false(fs.existsSync(configFile));
|
|
|
|
const config = await configUtils.loadConfig();
|
|
|
|
// The saved config file should now exist
|
|
t.true(fs.existsSync(configFile));
|
|
|
|
// And the contents should parse correctly to the config that was returned
|
|
t.deepEqual(fs.readFileSync(configFile, 'utf8'), JSON.stringify(config));
|
|
});
|
|
});
|
|
|
|
test("load input outside of workspace", async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
setInput('config-file', '../input');
|
|
|
|
try {
|
|
await configUtils.loadConfig();
|
|
throw new Error('loadConfig did not throw error');
|
|
} catch (err) {
|
|
t.deepEqual(err, new Error(configUtils.getConfigFileOutsideWorkspaceErrorMessage(path.join(tmpDir, '../input'))));
|
|
}
|
|
});
|
|
});
|
|
|
|
test("load non-local input with invalid repo syntax", async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
// no filename given, just a repo
|
|
setInput('config-file', 'octo-org/codeql-config@main');
|
|
|
|
try {
|
|
await configUtils.loadConfig();
|
|
throw new Error('loadConfig did not throw error');
|
|
} catch (err) {
|
|
t.deepEqual(err, new Error(configUtils.getConfigFileRepoFormatInvalid('octo-org/codeql-config@main')));
|
|
}
|
|
});
|
|
});
|
|
|
|
test("load non-existent input", async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
t.false(fs.existsSync(path.join(tmpDir, 'input')));
|
|
setInput('config-file', 'input');
|
|
|
|
try {
|
|
await configUtils.loadConfig();
|
|
throw new Error('loadConfig did not throw error');
|
|
} catch (err) {
|
|
t.deepEqual(err, new Error(configUtils.getConfigFileDoesNotExistErrorMessage(path.join(tmpDir, 'input'))));
|
|
}
|
|
});
|
|
});
|
|
|
|
test("load non-empty input", async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
// Just create a generic config object with non-default values for all fields
|
|
const inputFileContents = `
|
|
name: my config
|
|
disable-default-queries: true
|
|
queries:
|
|
- uses: ./
|
|
- uses: ./foo
|
|
- uses: foo/bar@dev
|
|
paths-ignore:
|
|
- a
|
|
- b
|
|
paths:
|
|
- c/d`;
|
|
|
|
fs.mkdirSync(path.join(tmpDir, 'foo'));
|
|
|
|
// And the config we expect it to parse to
|
|
const expectedConfig = new configUtils.Config();
|
|
expectedConfig.name = 'my config';
|
|
expectedConfig.disableDefaultQueries = true;
|
|
expectedConfig.additionalQueries.push(fs.realpathSync(tmpDir));
|
|
expectedConfig.additionalQueries.push(fs.realpathSync(path.join(tmpDir, 'foo')));
|
|
expectedConfig.externalQueries = [new configUtils.ExternalQuery('foo/bar', 'dev')];
|
|
expectedConfig.pathsIgnore = ['a', 'b'];
|
|
expectedConfig.paths = ['c/d'];
|
|
|
|
fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8');
|
|
setInput('config-file', 'input');
|
|
|
|
const actualConfig = await configUtils.loadConfig();
|
|
|
|
// Should exactly equal the object we constructed earlier
|
|
t.deepEqual(actualConfig, expectedConfig);
|
|
});
|
|
});
|
|
|
|
test("Octokit not used when reading local config", async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
const sandbox = sinon.createSandbox();
|
|
const spyKit = sandbox.spy(octokit, "Octokit");
|
|
|
|
const inputFileContents = `
|
|
name: my config
|
|
disable-default-queries: true
|
|
queries:
|
|
- uses: ./
|
|
paths-ignore:
|
|
- a
|
|
- b
|
|
paths:
|
|
- c/d`;
|
|
|
|
fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8');
|
|
setInput('config-file', 'input');
|
|
await configUtils.loadConfig();
|
|
t.false(spyKit.called);
|
|
sandbox.restore();
|
|
});
|
|
});
|
|
|
|
test("Remote and local configuration paths correctly identified", t => {
|
|
// If the path starts with ./, look locally
|
|
t.assert(configUtils.isLocal('./file'));
|
|
t.assert(configUtils.isLocal('./file@name'));
|
|
|
|
// Otherwise, if the path contains @ (branch specifier), assume it's a remote repo
|
|
t.false(configUtils.isLocal('octo-org/codeql-config/config.yaml@main'));
|
|
|
|
// Otherwise look locally (this is the fallback)
|
|
t.assert(configUtils.isLocal('file'));
|
|
});
|
|
|
|
test("Octokit used when reading remote config", 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
|
|
queries:
|
|
- uses: ./
|
|
paths-ignore:
|
|
- a
|
|
- b
|
|
paths:
|
|
- c/d`;
|
|
const dummyResponse = [
|
|
{data: inputFileContents}
|
|
];
|
|
|
|
let ok = new octokit.Octokit({
|
|
userAgent: "CodeQL Action",
|
|
});
|
|
const repos = ok.repos;
|
|
const spyGetContents = sinon.stub(repos, "getContents").resolves(Promise.resolve(dummyResponse));
|
|
ok.repos = repos;
|
|
sinon.stub(octokit, "Octokit").resolves(ok);
|
|
|
|
setInput('config-file', 'octo-org/codeql-config/config.yaml@main');
|
|
await configUtils.loadConfig();
|
|
t.assert(spyGetContents.called);
|
|
});
|
|
});
|
|
|
|
function doInvalidInputTest(
|
|
testName: string,
|
|
inputFileContents: string,
|
|
expectedErrorMessageGenerator: (configFile: string) => string) {
|
|
|
|
test("load invalid input - " + testName, async t => {
|
|
return await util.withTmpDir(async tmpDir => {
|
|
process.env['RUNNER_TEMP'] = tmpDir;
|
|
process.env['GITHUB_WORKSPACE'] = tmpDir;
|
|
|
|
const inputFile = path.join(tmpDir, 'input');
|
|
fs.writeFileSync(inputFile, inputFileContents, 'utf8');
|
|
setInput('config-file', 'input');
|
|
|
|
try {
|
|
await configUtils.loadConfig();
|
|
throw new Error('loadConfig 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: string,
|
|
expectedErrorMessageGenerator: (configFile: string) => string) {
|
|
|
|
// 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, ".."));
|