Files
codeql-action/src/feature-flags/properties.test.ts
2026-10-14 14:50:54 +01:00

244 lines
7.1 KiB
TypeScript

import test from "ava";
import * as sinon from "sinon";
import * as api from "../api-client";
import { parseRepositoryNwo } from "../repository";
import { RecordingLogger, setupTests } from "../testing-utils";
import * as properties from "./properties";
setupTests(test);
test.serial(
"loadPropertiesFromApi throws if response data is not an array",
async (t) => {
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: {},
});
const logger = new RecordingLogger();
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.throwsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
{
message: /Expected repository properties API to return an array/,
},
);
},
);
test.serial(
"loadPropertiesFromApi throws if response data contains objects without `property_name`",
async (t) => {
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [{}],
});
const logger = new RecordingLogger();
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.throwsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
{
message:
/Expected repository property object to have a 'property_name'/,
},
);
},
);
test.serial(
"loadPropertiesFromApi does not throw for unexpected value types of unknown properties",
async (t) => {
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [
{ property_name: "not-used-by-us", value: { foo: "bar" } },
{ property_name: "also-not-used-by-us", value: ["A", "B", "C"] },
],
});
const logger = new RecordingLogger();
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.notThrowsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
);
},
);
test.serial("loadPropertiesFromApi loads known properties", async (t) => {
const knownProperties = [
{ property_name: "github-codeql-config-file", value: "owner/repo" },
{ property_name: "github-codeql-extra-queries", value: "+queries" },
{ property_name: "github-codeql-tools", value: "nightly" },
];
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [
...knownProperties,
{ property_name: "unknown-property", value: "something" },
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
logger,
mockRepositoryNwo,
);
t.deepEqual(
response,
Object.fromEntries(
knownProperties.map((prop) => [prop.property_name, prop.value]),
),
);
});
test.serial("loadPropertiesFromApi parses true boolean property", async (t) => {
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [
{
property_name: "github-codeql-disable-overlay",
value: "true",
},
{ property_name: "github-codeql-extra-queries", value: "+queries" },
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const warningSpy = sinon.spy(logger, "warning");
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
logger,
mockRepositoryNwo,
);
t.deepEqual(response, {
"github-codeql-disable-overlay": true,
"github-codeql-extra-queries": "+queries",
});
t.true(warningSpy.notCalled);
});
test.serial(
"loadPropertiesFromApi parses false boolean property",
async (t) => {
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [
{
property_name: "github-codeql-disable-overlay",
value: "false",
},
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const warningSpy = sinon.spy(logger, "warning");
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
logger,
mockRepositoryNwo,
);
t.deepEqual(response, {
"github-codeql-disable-overlay": false,
});
t.true(warningSpy.notCalled);
},
);
test.serial(
"loadPropertiesFromApi throws if known property value is not a string",
async (t) => {
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [{ property_name: "github-codeql-extra-queries", value: 123 }],
});
const logger = new RecordingLogger();
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
await t.throwsAsync(
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
{
message:
/Unexpected value for repository property 'github-codeql-extra-queries' \(number\), got: 123/,
},
);
},
);
test.serial(
"loadPropertiesFromApi warns if boolean property has unexpected value",
async (t) => {
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [
{
property_name: "github-codeql-disable-overlay",
value: "yes",
},
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const warningSpy = sinon.spy(logger, "warning");
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
logger,
mockRepositoryNwo,
);
t.deepEqual(response, {
"github-codeql-disable-overlay": false,
});
t.true(warningSpy.calledOnce);
t.is(
warningSpy.firstCall.args[0],
"Repository property 'github-codeql-disable-overlay' has unexpected value 'yes'. Expected 'true' or 'false'. Defaulting to false.",
);
},
);
test.serial(
"loadPropertiesFromApi warns if a repository property name starts with the common prefix, but is not recognised by us",
async (t) => {
process.env["GITHUB_EVENT_NAME"] = "push";
const propertyName: string = `${properties.GITHUB_CODEQL_PROPERTY_PREFIX}unknown`;
sinon.stub(api, "getRepositoryProperties").resolves({
headers: {},
status: 200,
url: "",
data: [
{
property_name: propertyName,
value: "true",
},
] satisfies properties.GitHubPropertiesResponse,
});
const logger = new RecordingLogger();
const warningSpy = sinon.spy(logger, "warning");
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const response = await properties.loadPropertiesFromApi(
logger,
mockRepositoryNwo,
);
t.deepEqual(response, {});
t.true(warningSpy.calledOnce);
t.assert(
warningSpy.firstCall.args[0]
.toString()
.startsWith(
`Found repository properties ('${propertyName}'), which look like CodeQL Action repository properties`,
),
);
},
);