Skip to content

Commit 35d39df

Browse files
committed
Introduce type error when CodeQL is needed
1 parent 0d648eb commit 35d39df

File tree

3 files changed

+131
-102
lines changed

3 files changed

+131
-102
lines changed

src/feature-flags.test.ts

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
FeatureEnablement,
1111
Features,
1212
FEATURE_FLAGS_FILE_NAME,
13+
FeatureConfig,
14+
FeatureWithoutCLI,
1315
} from "./feature-flags";
1416
import { getRunnerLogger } from "./logging";
1517
import { parseRepositoryNwo } from "./repository";
@@ -46,7 +48,7 @@ test(`All features are disabled if running against GHES`, async (t) => {
4648

4749
for (const feature of Object.values(Feature)) {
4850
t.deepEqual(
49-
await features.getValue(feature, includeCodeQlIfRequired(feature)),
51+
await getFeatureIncludingCodeQlIfRequired(features, feature),
5052
featureConfig[feature].defaultValue,
5153
);
5254
}
@@ -75,9 +77,7 @@ test(`Feature flags are requested in GHEC-DR`, async (t) => {
7577

7678
for (const feature of Object.values(Feature)) {
7779
// Ensure we have gotten a response value back from the Mock API
78-
t.assert(
79-
await features.getValue(feature, includeCodeQlIfRequired(feature)),
80-
);
80+
t.assert(await getFeatureIncludingCodeQlIfRequired(features, feature));
8181
}
8282

8383
// And that we haven't bailed preemptively.
@@ -104,7 +104,7 @@ test("API response missing and features use default value", async (t) => {
104104

105105
for (const feature of Object.values(Feature)) {
106106
t.assert(
107-
(await features.getValue(feature, includeCodeQlIfRequired(feature))) ===
107+
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
108108
featureConfig[feature].defaultValue,
109109
);
110110
}
@@ -124,7 +124,7 @@ test("Features use default value if they're not returned in API response", async
124124

125125
for (const feature of Object.values(Feature)) {
126126
t.assert(
127-
(await features.getValue(feature, includeCodeQlIfRequired(feature))) ===
127+
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
128128
featureConfig[feature].defaultValue,
129129
);
130130
}
@@ -151,7 +151,7 @@ test("Include no more than 25 features in each API request", async (t) => {
151151
// from the API.
152152
const feature = Object.values(Feature)[0];
153153
await t.notThrowsAsync(async () =>
154-
features.getValue(feature, includeCodeQlIfRequired(feature)),
154+
getFeatureIncludingCodeQlIfRequired(features, feature),
155155
);
156156
});
157157
});
@@ -165,8 +165,7 @@ test("Feature flags exception is propagated if the API request errors", async (t
165165
const someFeature = Object.values(Feature)[0];
166166

167167
await t.throwsAsync(
168-
async () =>
169-
features.getValue(someFeature, includeCodeQlIfRequired(someFeature)),
168+
async () => getFeatureIncludingCodeQlIfRequired(features, someFeature),
170169
{
171170
message:
172171
"Encountered an error while trying to determine feature enablement: Error: some error message",
@@ -190,9 +189,9 @@ for (const feature of Object.keys(featureConfig)) {
190189
// retrieve the values of the actual features
191190
const actualFeatureEnablement: { [feature: string]: boolean } = {};
192191
for (const f of Object.keys(featureConfig)) {
193-
actualFeatureEnablement[f] = await features.getValue(
192+
actualFeatureEnablement[f] = await getFeatureIncludingCodeQlIfRequired(
193+
features,
194194
f as Feature,
195-
includeCodeQlIfRequired(f),
196195
);
197196
}
198197

@@ -210,19 +209,16 @@ for (const feature of Object.keys(featureConfig)) {
210209

211210
// feature should be disabled initially
212211
t.assert(
213-
!(await features.getValue(
212+
!(await getFeatureIncludingCodeQlIfRequired(
213+
features,
214214
feature as Feature,
215-
includeCodeQlIfRequired(feature),
216215
)),
217216
);
218217

219218
// set env var to true and check that the feature is now enabled
220219
process.env[featureConfig[feature].envVar] = "true";
221220
t.assert(
222-
await features.getValue(
223-
feature as Feature,
224-
includeCodeQlIfRequired(feature),
225-
),
221+
await getFeatureIncludingCodeQlIfRequired(features, feature as Feature),
226222
);
227223
});
228224
});
@@ -236,18 +232,15 @@ for (const feature of Object.keys(featureConfig)) {
236232

237233
// feature should be enabled initially
238234
t.assert(
239-
await features.getValue(
240-
feature as Feature,
241-
includeCodeQlIfRequired(feature),
242-
),
235+
await getFeatureIncludingCodeQlIfRequired(features, feature as Feature),
243236
);
244237

245238
// set env var to false and check that the feature is now disabled
246239
process.env[featureConfig[feature].envVar] = "false";
247240
t.assert(
248-
!(await features.getValue(
241+
!(await getFeatureIncludingCodeQlIfRequired(
242+
features,
249243
feature as Feature,
250-
includeCodeQlIfRequired(feature),
251244
)),
252245
);
253246
});
@@ -264,13 +257,16 @@ for (const feature of Object.keys(featureConfig)) {
264257
const expectedFeatureEnablement = initializeFeatures(true);
265258
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
266259

267-
await t.throwsAsync(async () => features.getValue(feature as Feature), {
268-
message: `Internal error: A ${
269-
featureConfig[feature].minimumVersion !== undefined
270-
? "minimum version"
271-
: "required tools feature"
272-
} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
273-
});
260+
await t.throwsAsync(
261+
async () => features.getValue(feature as FeatureWithoutCLI),
262+
{
263+
message: `Internal error: A ${
264+
featureConfig[feature].minimumVersion !== undefined
265+
? "minimum version"
266+
: "required tools feature"
267+
} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
268+
},
269+
);
274270
});
275271
});
276272
}
@@ -354,9 +350,9 @@ test("Feature flags are saved to disk", async (t) => {
354350
);
355351

356352
t.true(
357-
await features.getValue(
353+
await getFeatureIncludingCodeQlIfRequired(
354+
features,
358355
Feature.QaTelemetryEnabled,
359-
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
360356
),
361357
"Feature flag should be enabled initially",
362358
);
@@ -382,9 +378,9 @@ test("Feature flags are saved to disk", async (t) => {
382378
(features as any).gitHubFeatureFlags.cachedApiResponse = undefined;
383379

384380
t.false(
385-
await features.getValue(
381+
await getFeatureIncludingCodeQlIfRequired(
382+
features,
386383
Feature.QaTelemetryEnabled,
387-
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
388384
),
389385
"Feature flag should be enabled after reading from cached file",
390386
);
@@ -399,9 +395,9 @@ test("Environment variable can override feature flag cache", async (t) => {
399395

400396
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
401397
t.true(
402-
await features.getValue(
398+
await getFeatureIncludingCodeQlIfRequired(
399+
features,
403400
Feature.QaTelemetryEnabled,
404-
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
405401
),
406402
"Feature flag should be enabled initially",
407403
);
@@ -413,9 +409,9 @@ test("Environment variable can override feature flag cache", async (t) => {
413409
process.env.CODEQL_ACTION_QA_TELEMETRY = "false";
414410

415411
t.false(
416-
await features.getValue(
412+
await getFeatureIncludingCodeQlIfRequired(
413+
features,
417414
Feature.QaTelemetryEnabled,
418-
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
419415
),
420416
"Feature flag should be disabled after setting env var",
421417
);
@@ -512,7 +508,7 @@ for (const variant of [GitHubVariant.DOTCOM, GitHubVariant.GHEC_DR]) {
512508

513509
test("legacy feature flags should end with _enabled", async (t) => {
514510
for (const [feature, config] of Object.entries(featureConfig)) {
515-
if (config.legacyApi) {
511+
if ((config satisfies FeatureConfig as FeatureConfig).legacyApi) {
516512
t.assert(
517513
feature.endsWith("_enabled"),
518514
`legacy feature ${feature} should end with '_enabled'`,
@@ -523,7 +519,7 @@ test("legacy feature flags should end with _enabled", async (t) => {
523519

524520
test("non-legacy feature flags should not end with _enabled", async (t) => {
525521
for (const [feature, config] of Object.entries(featureConfig)) {
526-
if (!config.legacyApi) {
522+
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
527523
t.false(
528524
feature.endsWith("_enabled"),
529525
`non-legacy feature ${feature} should not end with '_enabled'`,
@@ -534,7 +530,7 @@ test("non-legacy feature flags should not end with _enabled", async (t) => {
534530

535531
test("non-legacy feature flags should not start with codeql_action_", async (t) => {
536532
for (const [feature, config] of Object.entries(featureConfig)) {
537-
if (!config.legacyApi) {
533+
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
538534
t.false(
539535
feature.startsWith("codeql_action_"),
540536
`non-legacy feature ${feature} should not start with 'codeql_action_'`,
@@ -573,12 +569,25 @@ function setUpFeatureFlagTests(
573569
* Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the
574570
* minimum version or tool feature requirements specified by the feature.
575571
*/
576-
function includeCodeQlIfRequired(feature: string) {
577-
return featureConfig[feature].minimumVersion !== undefined ||
578-
featureConfig[feature].toolsFeature !== undefined
579-
? mockCodeQLVersion(
580-
"9.9.9",
581-
Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])),
582-
)
583-
: undefined;
572+
function getFeatureIncludingCodeQlIfRequired(
573+
features: FeatureEnablement,
574+
feature: Feature,
575+
) {
576+
const config = featureConfig[
577+
feature
578+
] satisfies FeatureConfig as FeatureConfig;
579+
if (
580+
config.minimumVersion === undefined &&
581+
config.toolsFeature === undefined
582+
) {
583+
return features.getValue(feature as FeatureWithoutCLI);
584+
}
585+
586+
return features.getValue(
587+
feature,
588+
mockCodeQLVersion(
589+
"9.9.9",
590+
Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])),
591+
),
592+
);
584593
}

0 commit comments

Comments
 (0)