Files
codeql-action/src/config/file.test.ts
2026-07-28 12:02:41 +01:00

167 lines
5.9 KiB
TypeScript

import * as github from "@actions/github";
import test from "ava";
import sinon from "sinon";
import { AnalysisKind } from "../analyses";
import * as api from "../api-client";
import { RegistryProxyVars } from "../environment";
import { Feature } from "../feature-flags";
import { RepositoryPropertyName } from "../feature-flags/properties";
import {
callee,
SAMPLE_DOTCOM_API_DETAILS,
setupTests,
} from "../testing-utils";
import { getConfigFileInput, getRemoteConfig } from "./file";
setupTests(test);
test("getConfigFileInput returns undefined by default", async (t) => {
await callee(getConfigFileInput)
.withArgs({}, undefined)
.withFeatures([Feature.ConfigFileRepositoryProperty])
.passes(t.is, undefined);
});
const repositoryProperties = {
[RepositoryPropertyName.CONFIG_FILE]: "/path/from/property",
};
test("getConfigFileInput returns input value", async (t) => {
const testInput = "/some/path";
// Even though both an input and repository property are configured,
// we prefer the direct input to the Action.
await callee(getConfigFileInput)
.withFeatures([Feature.ConfigFileRepositoryProperty])
.withActions((actionsEnv) => {
sinon
.stub(actionsEnv, "getOptionalInput")
.withArgs("config-file")
.returns(testInput);
})
.withArgs(repositoryProperties, undefined)
.logs(t, "Using configuration file input from workflow")
.passes(t.is, testInput);
});
test("getConfigFileInput returns repository property value", async (t) => {
// Since there is no direct input, we should use the repository property.
await callee(getConfigFileInput)
.withFeatures([Feature.ConfigFileRepositoryProperty])
.withArgs(repositoryProperties, undefined)
.logs(t, "Using configuration file input from repository property")
.passes(t.is, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]);
});
test("getConfigFileInput returns repository property value for Code Scanning", async (t) => {
// Since there is no direct input, we should use the repository property.
await callee(getConfigFileInput)
.withFeatures([Feature.ConfigFileRepositoryProperty])
.withArgs(repositoryProperties, [AnalysisKind.CodeScanning])
.logs(t, "Using configuration file input from repository property")
.passes(t.is, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]);
});
test("getConfigFileInput ignores repository property for other analysis kinds", async (t) => {
const unsupportedCases = [
[AnalysisKind.CodeQuality],
[AnalysisKind.RiskAssessment],
[AnalysisKind.CodeScanning, AnalysisKind.CodeQuality],
];
const target = callee(getConfigFileInput).withFeatures([
Feature.ConfigFileRepositoryProperty,
]);
for (const unsupportedCase of unsupportedCases) {
// Since the analysis kind is unsupported, we should ignore the repository property.
await target
.withArgs(repositoryProperties, unsupportedCase)
.logs(
t,
"Ignoring configuration file input from repository property, because it is unsupported for the current analysis kind.",
)
.passes(t.is, undefined);
}
});
test("getConfigFileInput ignores empty repository property value", async (t) => {
// Since the repository property value is an empty/whitespace string, we should ignore it.
await callee(getConfigFileInput)
.withFeatures([Feature.ConfigFileRepositoryProperty])
.withArgs({ [RepositoryPropertyName.CONFIG_FILE]: " " }, undefined)
.passes(t.is, undefined);
});
test("getConfigFileInput ignores repository property value when FF is off", async (t) => {
// Since the FF is off, we should ignore the repository property value.
await callee(getConfigFileInput)
.withFeatures([])
.withArgs(repositoryProperties, undefined)
.notLogs(t, "Using configuration file input from repository property")
.logs(
t,
"Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.",
)
.passes(t.is, undefined);
});
test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => {
const client = github.getOctokit("123");
const response = {
data: {
content: Buffer.from("disable-default-queries: false").toString("base64"),
},
};
sinon
.stub(client.rest.repos, "getContent")
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
.resolves(response as any);
// We stub `getApiClientWithExternalAuth` so that it throws if no
// proxy is provided and returns the client otherwise. This allows us
// to verify the result in the following test cases.
const errorMessage = "No `proxy` was provided by the caller.";
sinon
.stub(api, "getApiClientWithExternalAuth")
.callsFake((_details, proxy) => {
// Throw if proxy isn't defined.
if (proxy === undefined) {
throw new Error(errorMessage);
}
// Otherwise return the client object.
return client;
});
const target = callee(getRemoteConfig)
.withDefaultActionsEnv()
.withArgs("file.yml", SAMPLE_DOTCOM_API_DETAILS);
// Should use it when the FF is enabled and the environment variables are set.
await target
.withFeatures([Feature.ProxyApiRequests])
.withEnv((env) => {
env.set(RegistryProxyVars.PROXY_HOST, "localhost");
env.set(RegistryProxyVars.PROXY_PORT, "1234");
})
.logs(t, "Using private registry proxy at 'http://localhost:1234'")
.passes(t.truthy);
// But not when the FF is not enabled.
await target
.withEnv((env) => {
env.set(RegistryProxyVars.PROXY_HOST, "localhost");
env.set(RegistryProxyVars.PROXY_PORT, "1234");
})
.notLogs(t, "Using private registry proxy at 'http://localhost:1234'")
.throws(t, { message: errorMessage });
// And not when the environment variables aren't set.
await target
.withFeatures([Feature.ProxyApiRequests])
.notLogs(t, "Using private registry proxy at 'http://localhost:1234'")
.throws(t, { message: errorMessage });
});