Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/src/config-utils.test.ts
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Sam Partington
Add validation of remote config location, no retrieval yet
282 lines (233 sloc)
8.18 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 spyKit = sinon.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); | |
}); | |
}); | |
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')); | |
}); | |
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, "..")); |