Merge remote-tracking branch 'origin/main' into koesie10/improve-test-setup
This commit is contained in:
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@@ -94,6 +94,23 @@ jobs:
|
||||
asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Create sourcemap ZIP file
|
||||
run: |
|
||||
cd dist/vscode-codeql/out
|
||||
zip -r ../../vscode-codeql-sourcemaps.zip *.map
|
||||
|
||||
- name: Upload sourcemap ZIP file
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
if: success()
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# Get the `upload_url` from the `create-release` step above.
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: dist/vscode-codeql-sourcemaps.zip
|
||||
asset_name: ${{ format('vscode-codeql-sourcemaps-{0}.zip', steps.prepare-artifacts.outputs.ref_name) }}
|
||||
asset_content_type: application/zip
|
||||
|
||||
###
|
||||
# Do Post release work: version bump and changelog PR
|
||||
# Only do this if we are running from a PR (ie- this is part of the release process)
|
||||
|
||||
@@ -229,6 +229,13 @@ Pre-recorded scenarios are stored in `./src/mocks/scenarios`. However, it's poss
|
||||
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
|
||||
* Go through all recent PRs and make sure they are properly accounted for.
|
||||
* Make sure all changelog entries have links back to their PR(s) if appropriate.
|
||||
* For picking the new version number, we default to increasing the patch version number, but make our own judgement about whether a change is big enough to warrant a minor version bump. Common reasons for a minor bump could include:
|
||||
* Making substantial new features available to all users. This can include lifting a feature flag.
|
||||
* Breakage in compatibility with recent versions of the CLI.
|
||||
* Minimum required version of VS Code is increased.
|
||||
* New telemetry events are added.
|
||||
* Deprecation or removal of commands.
|
||||
* Accumulation of many changes, none of which are individually big enough to warrant a minor bump, but which together are. This does not include changes which are purely internal to the extension, such as refactoring, or which are only available behind a feature flag.
|
||||
1. Double-check that the node version we're using matches the one used for VS Code. If it doesn't, you will then need to update the node version in the following files:
|
||||
* `.nvmrc` - this will enable `nvm` to automatically switch to the correct node version when you're in the project folder
|
||||
* `.github/workflows/main.yml` - all the "node-version: <version>" settings
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.openConfigFile",
|
||||
"title": "Open Database Configuration File",
|
||||
"icon": "$(edit)"
|
||||
"icon": "$(json)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.addNewDatabase",
|
||||
@@ -379,7 +379,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItem",
|
||||
"title": "✓"
|
||||
"title": "Select"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
|
||||
@@ -822,7 +822,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItem",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeSelected/ && false",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeSelected/",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@ import { spawnSync } from "child_process";
|
||||
import { basename, resolve } from "path";
|
||||
import { pathExists, readJSON } from "fs-extra";
|
||||
import { RawSourceMap, SourceMapConsumer } from "source-map";
|
||||
import { Open } from "unzipper";
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.error(
|
||||
@@ -36,6 +37,12 @@ const versionNumber = process.argv[2].startsWith("v")
|
||||
const stacktrace = process.argv[3];
|
||||
|
||||
async function extractSourceMap() {
|
||||
const releaseAssetsDirectory = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"release-assets",
|
||||
versionNumber,
|
||||
);
|
||||
const sourceMapsDirectory = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
@@ -47,34 +54,64 @@ async function extractSourceMap() {
|
||||
if (!(await pathExists(sourceMapsDirectory))) {
|
||||
console.log("Downloading source maps...");
|
||||
|
||||
const workflowRuns = runGhJSON<WorkflowRunListItem[]>([
|
||||
"run",
|
||||
"list",
|
||||
"--workflow",
|
||||
"release.yml",
|
||||
"--branch",
|
||||
const release = runGhJSON<Release>([
|
||||
"release",
|
||||
"view",
|
||||
versionNumber,
|
||||
"--json",
|
||||
"databaseId,number",
|
||||
"id,name,assets",
|
||||
]);
|
||||
|
||||
if (workflowRuns.length !== 1) {
|
||||
throw new Error(
|
||||
`Expected exactly one workflow run for ${versionNumber}, got ${workflowRuns.length}`,
|
||||
const sourcemapAsset = release.assets.find(
|
||||
(asset) => asset.name === `vscode-codeql-sourcemaps-${versionNumber}.zip`,
|
||||
);
|
||||
|
||||
if (sourcemapAsset) {
|
||||
// This downloads a ZIP file of the source maps
|
||||
runGh([
|
||||
"release",
|
||||
"download",
|
||||
versionNumber,
|
||||
"--pattern",
|
||||
sourcemapAsset.name,
|
||||
"--dir",
|
||||
releaseAssetsDirectory,
|
||||
]);
|
||||
|
||||
const file = await Open.file(
|
||||
resolve(releaseAssetsDirectory, sourcemapAsset.name),
|
||||
);
|
||||
await file.extract({ path: sourceMapsDirectory });
|
||||
} else {
|
||||
const workflowRuns = runGhJSON<WorkflowRunListItem[]>([
|
||||
"run",
|
||||
"list",
|
||||
"--workflow",
|
||||
"release.yml",
|
||||
"--branch",
|
||||
versionNumber,
|
||||
"--json",
|
||||
"databaseId,number",
|
||||
]);
|
||||
|
||||
if (workflowRuns.length !== 1) {
|
||||
throw new Error(
|
||||
`Expected exactly one workflow run for ${versionNumber}, got ${workflowRuns.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowRun = workflowRuns[0];
|
||||
|
||||
runGh([
|
||||
"run",
|
||||
"download",
|
||||
workflowRun.databaseId.toString(),
|
||||
"--name",
|
||||
"vscode-codeql-sourcemaps",
|
||||
"--dir",
|
||||
sourceMapsDirectory,
|
||||
]);
|
||||
}
|
||||
|
||||
const workflowRun = workflowRuns[0];
|
||||
|
||||
runGh([
|
||||
"run",
|
||||
"download",
|
||||
workflowRun.databaseId.toString(),
|
||||
"--name",
|
||||
"vscode-codeql-sourcemaps",
|
||||
"--dir",
|
||||
sourceMapsDirectory,
|
||||
]);
|
||||
}
|
||||
|
||||
if (stacktrace.includes("at")) {
|
||||
@@ -172,6 +209,17 @@ function runGhJSON<T>(args: readonly string[]): T {
|
||||
return JSON.parse(runGh(args));
|
||||
}
|
||||
|
||||
type ReleaseAsset = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Release = {
|
||||
id: string;
|
||||
name: string;
|
||||
assets: ReleaseAsset[];
|
||||
};
|
||||
|
||||
type WorkflowRunListItem = {
|
||||
databaseId: number;
|
||||
number: number;
|
||||
|
||||
@@ -19,6 +19,7 @@ import { CancellationToken, Uri } from "vscode";
|
||||
import { ProgressCallback } from "../commandRunner";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { QLPACK_FILENAMES } from "../pure/ql";
|
||||
|
||||
export async function qlpackOfDatabase(
|
||||
cli: CodeQLCliServer,
|
||||
@@ -113,7 +114,7 @@ async function resolveContextualQuery(
|
||||
// Work out the enclosing pack.
|
||||
const packContents = await cli.packPacklist(query, false);
|
||||
const packFilePath = packContents.find((p) =>
|
||||
["codeql-pack.yml", "qlpack.yml"].includes(basename(p)),
|
||||
QLPACK_FILENAMES.includes(basename(p)),
|
||||
);
|
||||
if (packFilePath === undefined) {
|
||||
// Should not happen; we already resolved this query.
|
||||
|
||||
@@ -845,6 +845,7 @@ async function activateWithInstalledDistribution(
|
||||
documentSelector: [
|
||||
{ language: "ql", scheme: "file" },
|
||||
{ language: "yaml", scheme: "file", pattern: "**/qlpack.yml" },
|
||||
{ language: "yaml", scheme: "file", pattern: "**/codeql-pack.yml" },
|
||||
],
|
||||
synchronize: {
|
||||
configurationSection: "codeQL",
|
||||
|
||||
@@ -23,6 +23,7 @@ import { extLogger, OutputChannelLogger } from "./common";
|
||||
import { QueryMetadata } from "./pure/interface-types";
|
||||
import { telemetryListener } from "./telemetry";
|
||||
import { RedactableError } from "./pure/errors";
|
||||
import { getQlPackPath } from "./pure/ql";
|
||||
|
||||
// Shared temporary folder for the extension.
|
||||
export const tmpDir = dirSync({
|
||||
@@ -387,17 +388,22 @@ async function findDbschemePack(
|
||||
): Promise<{ name: string; isLibraryPack: boolean }> {
|
||||
for (const { packDir, packName } of packs) {
|
||||
if (packDir !== undefined) {
|
||||
const qlpack = load(
|
||||
await readFile(join(packDir, "qlpack.yml"), "utf8"),
|
||||
) as { dbscheme?: string; library?: boolean };
|
||||
if (
|
||||
qlpack.dbscheme !== undefined &&
|
||||
basename(qlpack.dbscheme) === basename(dbschemePath)
|
||||
) {
|
||||
return {
|
||||
name: packName,
|
||||
isLibraryPack: qlpack.library === true,
|
||||
const qlpackPath = await getQlPackPath(packDir);
|
||||
|
||||
if (qlpackPath !== undefined) {
|
||||
const qlpack = load(await readFile(qlpackPath, "utf8")) as {
|
||||
dbscheme?: string;
|
||||
library?: boolean;
|
||||
};
|
||||
if (
|
||||
qlpack.dbscheme !== undefined &&
|
||||
basename(qlpack.dbscheme) === basename(dbschemePath)
|
||||
) {
|
||||
return {
|
||||
name: packName,
|
||||
isLibraryPack: qlpack.library === true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
extensions/ql-vscode/src/pure/ql.ts
Normal file
19
extensions/ql-vscode/src/pure/ql.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { join } from "path";
|
||||
import { pathExists } from "fs-extra";
|
||||
|
||||
export const QLPACK_FILENAMES = ["qlpack.yml", "codeql-pack.yml"];
|
||||
export const FALLBACK_QLPACK_FILENAME = QLPACK_FILENAMES[0];
|
||||
|
||||
export async function getQlPackPath(
|
||||
packRoot: string,
|
||||
): Promise<string | undefined> {
|
||||
for (const filename of QLPACK_FILENAMES) {
|
||||
const path = join(packRoot, filename);
|
||||
|
||||
if (await pathExists(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "./helpers";
|
||||
import { ProgressCallback, UserCancellationException } from "./commandRunner";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
import { FALLBACK_QLPACK_FILENAME, getQlPackPath } from "./pure/ql";
|
||||
|
||||
const QUICK_QUERIES_DIR_NAME = "quick-queries";
|
||||
const QUICK_QUERY_QUERY_NAME = "quick-query.ql";
|
||||
@@ -112,7 +113,7 @@ export async function displayQuickQuery(
|
||||
const dbscheme = await getPrimaryDbscheme(datasetFolder);
|
||||
const qlpack = (await getQlPackForDbscheme(cliServer, dbscheme))
|
||||
.dbschemePack;
|
||||
const qlPackFile = join(queriesDir, "qlpack.yml");
|
||||
const qlPackFile = await getQlPackPath(queriesDir);
|
||||
const qlFile = join(queriesDir, QUICK_QUERY_QUERY_NAME);
|
||||
const shouldRewrite = await checkShouldRewrite(qlPackFile, qlpack);
|
||||
|
||||
@@ -126,7 +127,7 @@ export async function displayQuickQuery(
|
||||
},
|
||||
};
|
||||
await writeFile(
|
||||
qlPackFile,
|
||||
qlPackFile ?? join(queriesDir, FALLBACK_QLPACK_FILENAME),
|
||||
QLPACK_FILE_HEADER + dump(quickQueryQlpackYaml),
|
||||
"utf8",
|
||||
);
|
||||
@@ -158,7 +159,13 @@ export async function displayQuickQuery(
|
||||
}
|
||||
}
|
||||
|
||||
async function checkShouldRewrite(qlPackFile: string, newDependency: string) {
|
||||
async function checkShouldRewrite(
|
||||
qlPackFile: string | undefined,
|
||||
newDependency: string,
|
||||
) {
|
||||
if (!qlPackFile) {
|
||||
return true;
|
||||
}
|
||||
if (!(await pathExists(qlPackFile))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CancellationToken, Uri, window } from "vscode";
|
||||
import { relative, join, sep, dirname, parse, basename } from "path";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { pathExists, copy, writeFile, readFile, mkdirp } from "fs-extra";
|
||||
import { copy, writeFile, readFile, mkdirp } from "fs-extra";
|
||||
import { dir, tmpName } from "tmp-promise";
|
||||
import {
|
||||
askForLanguage,
|
||||
@@ -30,6 +30,11 @@ import {
|
||||
} from "./repository-selection";
|
||||
import { Repository } from "./shared/repository";
|
||||
import { DbManager } from "../databases/db-manager";
|
||||
import {
|
||||
getQlPackPath,
|
||||
FALLBACK_QLPACK_FILENAME,
|
||||
QLPACK_FILENAMES,
|
||||
} from "../pure/ql";
|
||||
|
||||
export interface QlPack {
|
||||
name: string;
|
||||
@@ -67,7 +72,7 @@ async function generateQueryPack(
|
||||
const targetQueryFileName = join(queryPackDir, packRelativePath);
|
||||
|
||||
let language: string | undefined;
|
||||
if (await getExistingPackFile(originalPackRoot)) {
|
||||
if (await getQlPackPath(originalPackRoot)) {
|
||||
// don't include ql files. We only want the queryFile to be copied.
|
||||
const toCopy = await cliServer.packPacklist(originalPackRoot, false);
|
||||
|
||||
@@ -120,7 +125,10 @@ async function generateQueryPack(
|
||||
},
|
||||
defaultSuite: generateDefaultSuite(packRelativePath),
|
||||
};
|
||||
await writeFile(join(queryPackDir, "qlpack.yml"), dump(syntheticQueryPack));
|
||||
await writeFile(
|
||||
join(queryPackDir, FALLBACK_QLPACK_FILENAME),
|
||||
dump(syntheticQueryPack),
|
||||
);
|
||||
}
|
||||
if (!language) {
|
||||
throw new UserCancellationException("Could not determine language.");
|
||||
@@ -163,7 +171,7 @@ async function generateQueryPack(
|
||||
async function findPackRoot(queryFile: string): Promise<string> {
|
||||
// recursively find the directory containing qlpack.yml
|
||||
let dir = dirname(queryFile);
|
||||
while (!(await getExistingPackFile(dir))) {
|
||||
while (!(await getQlPackPath(dir))) {
|
||||
dir = dirname(dir);
|
||||
if (isFileSystemRoot(dir)) {
|
||||
// there is no qlpack.yml in this directory or any parent directory.
|
||||
@@ -175,16 +183,6 @@ async function findPackRoot(queryFile: string): Promise<string> {
|
||||
return dir;
|
||||
}
|
||||
|
||||
async function getExistingPackFile(dir: string) {
|
||||
if (await pathExists(join(dir, "qlpack.yml"))) {
|
||||
return join(dir, "qlpack.yml");
|
||||
}
|
||||
if (await pathExists(join(dir, "codeql-pack.yml"))) {
|
||||
return join(dir, "codeql-pack.yml");
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isFileSystemRoot(dir: string): boolean {
|
||||
const pathObj = parse(dir);
|
||||
return pathObj.root === dir && pathObj.base === "";
|
||||
@@ -319,12 +317,14 @@ async function fixPackFile(
|
||||
queryPackDir: string,
|
||||
packRelativePath: string,
|
||||
): Promise<void> {
|
||||
const packPath = await getExistingPackFile(queryPackDir);
|
||||
const packPath = await getQlPackPath(queryPackDir);
|
||||
|
||||
// This should not happen since we create the pack ourselves.
|
||||
if (!packPath) {
|
||||
throw new Error(
|
||||
`Could not find qlpack.yml or codeql-pack.yml file in '${queryPackDir}'`,
|
||||
`Could not find ${QLPACK_FILENAMES.join(
|
||||
" or ",
|
||||
)} file in '${queryPackDir}'`,
|
||||
);
|
||||
}
|
||||
const qlpack = load(await readFile(packPath, "utf8")) as QlPack;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"v2.12.1",
|
||||
"v2.12.2",
|
||||
"v2.11.6",
|
||||
"v2.7.6",
|
||||
"v2.8.5",
|
||||
|
||||
48
extensions/ql-vscode/test/unit-tests/pure/ql.test.ts
Normal file
48
extensions/ql-vscode/test/unit-tests/pure/ql.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { join } from "path";
|
||||
import { dirSync } from "tmp-promise";
|
||||
import { DirResult } from "tmp";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { getQlPackPath } from "../../../src/pure/ql";
|
||||
|
||||
describe("getQlPackPath", () => {
|
||||
let tmpDir: DirResult;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = dirSync({
|
||||
prefix: "queries_",
|
||||
keep: false,
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tmpDir.removeCallback();
|
||||
});
|
||||
|
||||
it("should find a qlpack.yml when it exists", async () => {
|
||||
await writeFile(join(tmpDir.name, "qlpack.yml"), "name: test");
|
||||
|
||||
const result = await getQlPackPath(tmpDir.name);
|
||||
expect(result).toEqual(join(tmpDir.name, "qlpack.yml"));
|
||||
});
|
||||
|
||||
it("should find a codeql-pack.yml when it exists", async () => {
|
||||
await writeFile(join(tmpDir.name, "codeql-pack.yml"), "name: test");
|
||||
|
||||
const result = await getQlPackPath(tmpDir.name);
|
||||
expect(result).toEqual(join(tmpDir.name, "codeql-pack.yml"));
|
||||
});
|
||||
|
||||
it("should find a qlpack.yml when both exist", async () => {
|
||||
await writeFile(join(tmpDir.name, "qlpack.yml"), "name: test");
|
||||
await writeFile(join(tmpDir.name, "codeql-pack.yml"), "name: test");
|
||||
|
||||
const result = await getQlPackPath(tmpDir.name);
|
||||
expect(result).toEqual(join(tmpDir.name, "qlpack.yml"));
|
||||
});
|
||||
|
||||
it("should find nothing when it doesn't exist", async () => {
|
||||
const result = await getQlPackPath(tmpDir.name);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,358 +0,0 @@
|
||||
import { join } from "path";
|
||||
import {
|
||||
CancellationTokenSource,
|
||||
commands,
|
||||
ExtensionContext,
|
||||
extensions,
|
||||
QuickPickItem,
|
||||
Uri,
|
||||
window,
|
||||
} from "vscode";
|
||||
import { load } from "js-yaml";
|
||||
|
||||
import { QlPack } from "../../../../src/remote-queries/run-remote-query";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
import { CodeQLExtensionInterface } from "../../../../src/extension";
|
||||
import {
|
||||
setRemoteControllerRepo,
|
||||
setRemoteRepositoryLists,
|
||||
} from "../../../../src/config";
|
||||
import { UserCancellationException } from "../../../../src/commandRunner";
|
||||
import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client";
|
||||
import { Repository } from "../../../../src/remote-queries/gh-api/repository";
|
||||
import { createMockExtensionContext } from "../../no-workspace";
|
||||
import { OutputChannelLogger } from "../../../../src/common";
|
||||
import { RemoteQueriesSubmission } from "../../../../src/remote-queries/shared/remote-queries";
|
||||
import { readBundledPack } from "../../utils/bundled-pack-helpers";
|
||||
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
||||
import {
|
||||
fixWorkspaceReferences,
|
||||
restoreWorkspaceReferences,
|
||||
} from "../../global.helper";
|
||||
import { createMockApp } from "../../../__mocks__/appMock";
|
||||
import { App } from "../../../../src/common/app";
|
||||
|
||||
// up to 3 minutes per test
|
||||
jest.setTimeout(3 * 60 * 1000);
|
||||
|
||||
describe("Remote queries", () => {
|
||||
const baseDir = join(__dirname, "..");
|
||||
|
||||
const qlpackFileWithWorkspaceRefs = getFile(
|
||||
"data-remote-qlpack/qlpack.yml",
|
||||
).fsPath;
|
||||
|
||||
let cli: CodeQLCliServer;
|
||||
let cancellationTokenSource: CancellationTokenSource;
|
||||
const progress = jest.fn();
|
||||
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
let getRepositoryFromNwoStub: jest.SpiedFunction<
|
||||
typeof ghApiClient.getRepositoryFromNwo
|
||||
>;
|
||||
let ctx: ExtensionContext;
|
||||
let logger: any;
|
||||
let app: App;
|
||||
let remoteQueriesManager: RemoteQueriesManager;
|
||||
|
||||
let originalDeps: Record<string, string> | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
showQuickPickSpy = jest.spyOn(window, "showQuickPick");
|
||||
getRepositoryFromNwoStub = jest.spyOn(ghApiClient, "getRepositoryFromNwo");
|
||||
|
||||
const extension = await extensions
|
||||
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
|
||||
"GitHub.vscode-codeql",
|
||||
)!
|
||||
.activate();
|
||||
if ("cliServer" in extension) {
|
||||
cli = extension.cliServer;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Extension not initialized. Make sure cli is downloaded and installed properly.",
|
||||
);
|
||||
}
|
||||
|
||||
ctx = createMockExtensionContext();
|
||||
|
||||
logger = new OutputChannelLogger("test-logger");
|
||||
app = createMockApp({});
|
||||
remoteQueriesManager = new RemoteQueriesManager(
|
||||
ctx,
|
||||
app,
|
||||
cli,
|
||||
"fake-storage-dir",
|
||||
logger,
|
||||
);
|
||||
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
// Should not have asked for a language
|
||||
showQuickPickSpy
|
||||
.mockResolvedValueOnce({
|
||||
repositories: ["github/vscode-codeql"],
|
||||
} as unknown as QuickPickItem)
|
||||
.mockResolvedValue("javascript" as unknown as QuickPickItem);
|
||||
|
||||
const dummyRepository: Repository = {
|
||||
id: 123,
|
||||
name: "vscode-codeql",
|
||||
full_name: "github/vscode-codeql",
|
||||
private: false,
|
||||
};
|
||||
getRepositoryFromNwoStub.mockResolvedValue(dummyRepository);
|
||||
|
||||
// always run in the vscode-codeql repo
|
||||
await setRemoteControllerRepo("github/vscode-codeql");
|
||||
await setRemoteRepositoryLists({
|
||||
"vscode-codeql": ["github/vscode-codeql"],
|
||||
});
|
||||
|
||||
// Only new version support `${workspace}` in qlpack.yml
|
||||
originalDeps = await fixWorkspaceReferences(
|
||||
qlpackFileWithWorkspaceRefs,
|
||||
cli,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await restoreWorkspaceReferences(qlpackFileWithWorkspaceRefs, originalDeps);
|
||||
});
|
||||
|
||||
describe("runRemoteQuery", () => {
|
||||
let mockSubmitRemoteQueries: jest.SpiedFunction<
|
||||
typeof ghApiClient.submitRemoteQueries
|
||||
>;
|
||||
let executeCommandSpy: jest.SpiedFunction<typeof commands.executeCommand>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSubmitRemoteQueries = jest
|
||||
.spyOn(ghApiClient, "submitRemoteQueries")
|
||||
.mockResolvedValue({
|
||||
workflow_run_id: 20,
|
||||
repositories_queried: ["octodemo/hello-world-1"],
|
||||
});
|
||||
|
||||
executeCommandSpy = jest.spyOn(commands, "executeCommand");
|
||||
});
|
||||
|
||||
it("should run a remote query that is part of a qlpack", async () => {
|
||||
const fileUri = getFile("data-remote-qlpack/in-pack.ql");
|
||||
|
||||
await remoteQueriesManager.runRemoteQuery(
|
||||
fileUri,
|
||||
progress,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(mockSubmitRemoteQueries).toBeCalledTimes(1);
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.monitorRemoteQuery",
|
||||
expect.any(String),
|
||||
expect.objectContaining({ queryFilePath: fileUri.fsPath }),
|
||||
);
|
||||
|
||||
const request: RemoteQueriesSubmission =
|
||||
mockSubmitRemoteQueries.mock.calls[0][1];
|
||||
|
||||
const packFS = await readBundledPack(request.queryPack);
|
||||
|
||||
// to retrieve the list of repositories
|
||||
expect(showQuickPickSpy).toBeCalledTimes(1);
|
||||
|
||||
expect(getRepositoryFromNwoStub).toBeCalledTimes(1);
|
||||
|
||||
// check a few files that we know should exist and others that we know should not
|
||||
expect(packFS.fileExists("in-pack.ql")).toBe(true);
|
||||
expect(packFS.fileExists("lib.qll")).toBe(true);
|
||||
expect(packFS.fileExists("qlpack.yml")).toBe(true);
|
||||
|
||||
// depending on the cli version, we should have one of these files
|
||||
expect(
|
||||
packFS.fileExists("qlpack.lock.yml") ||
|
||||
packFS.fileExists("codeql-pack.lock.yml"),
|
||||
).toBe(true);
|
||||
expect(packFS.fileExists("not-in-pack.ql")).toBe(false);
|
||||
|
||||
// should have generated a correct qlpack file
|
||||
const qlpackContents: any = load(
|
||||
packFS.fileContents("qlpack.yml").toString("utf-8"),
|
||||
);
|
||||
expect(qlpackContents.name).toBe("codeql-remote/query");
|
||||
|
||||
verifyQlPack("in-pack.ql", packFS.fileContents("qlpack.yml"), "0.0.0");
|
||||
|
||||
const libraryDir = ".codeql/libraries/codeql";
|
||||
const packNames = packFS.directoryContents(libraryDir).sort();
|
||||
|
||||
// check dependencies.
|
||||
expect(packNames).toContain("javascript-all");
|
||||
});
|
||||
|
||||
it("should run a remote query that is not part of a qlpack", async () => {
|
||||
const fileUri = getFile("data-remote-no-qlpack/in-pack.ql");
|
||||
|
||||
await remoteQueriesManager.runRemoteQuery(
|
||||
fileUri,
|
||||
progress,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(mockSubmitRemoteQueries).toBeCalledTimes(1);
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.monitorRemoteQuery",
|
||||
expect.any(String),
|
||||
expect.objectContaining({ queryFilePath: fileUri.fsPath }),
|
||||
);
|
||||
|
||||
const request: RemoteQueriesSubmission =
|
||||
mockSubmitRemoteQueries.mock.calls[0][1];
|
||||
|
||||
const packFS = await readBundledPack(request.queryPack);
|
||||
|
||||
// to retrieve the list of repositories
|
||||
// and a second time to ask for the language
|
||||
expect(showQuickPickSpy).toBeCalledTimes(2);
|
||||
|
||||
expect(getRepositoryFromNwoStub).toBeCalledTimes(1);
|
||||
|
||||
// check a few files that we know should exist and others that we know should not
|
||||
expect(packFS.fileExists("in-pack.ql")).toBe(true);
|
||||
expect(packFS.fileExists("qlpack.yml")).toBe(true);
|
||||
// depending on the cli version, we should have one of these files
|
||||
expect(
|
||||
packFS.fileExists("qlpack.lock.yml") ||
|
||||
packFS.fileExists("codeql-pack.lock.yml"),
|
||||
).toBe(true);
|
||||
expect(packFS.fileExists("lib.qll")).toBe(false);
|
||||
expect(packFS.fileExists("not-in-pack.ql")).toBe(false);
|
||||
|
||||
// the compiled pack
|
||||
verifyQlPack("in-pack.ql", packFS.fileContents("qlpack.yml"), "0.0.0");
|
||||
|
||||
// should have generated a correct qlpack file
|
||||
const qlpackContents: any = load(
|
||||
packFS.fileContents("qlpack.yml").toString("utf-8"),
|
||||
);
|
||||
expect(qlpackContents.name).toBe("codeql-remote/query");
|
||||
expect(qlpackContents.version).toBe("0.0.0");
|
||||
expect(qlpackContents.dependencies?.["codeql/javascript-all"]).toBe("*");
|
||||
|
||||
const libraryDir = ".codeql/libraries/codeql";
|
||||
const packNames = packFS.directoryContents(libraryDir).sort();
|
||||
|
||||
// check dependencies.
|
||||
expect(packNames).toContain("javascript-all");
|
||||
});
|
||||
|
||||
it("should run a remote query that is nested inside a qlpack", async () => {
|
||||
const fileUri = getFile("data-remote-qlpack-nested/subfolder/in-pack.ql");
|
||||
|
||||
await remoteQueriesManager.runRemoteQuery(
|
||||
fileUri,
|
||||
progress,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(mockSubmitRemoteQueries).toBeCalledTimes(1);
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.monitorRemoteQuery",
|
||||
expect.any(String),
|
||||
expect.objectContaining({ queryFilePath: fileUri.fsPath }),
|
||||
);
|
||||
|
||||
const request: RemoteQueriesSubmission =
|
||||
mockSubmitRemoteQueries.mock.calls[0][1];
|
||||
|
||||
const packFS = await readBundledPack(request.queryPack);
|
||||
|
||||
// to retrieve the list of repositories
|
||||
expect(showQuickPickSpy).toBeCalledTimes(1);
|
||||
|
||||
expect(getRepositoryFromNwoStub).toBeCalledTimes(1);
|
||||
|
||||
// check a few files that we know should exist and others that we know should not
|
||||
expect(packFS.fileExists("subfolder/in-pack.ql")).toBe(true);
|
||||
expect(packFS.fileExists("codeql-pack.yml")).toBe(true);
|
||||
// depending on the cli version, we should have one of these files
|
||||
expect(
|
||||
packFS.fileExists("qlpack.lock.yml") ||
|
||||
packFS.fileExists("codeql-pack.lock.yml"),
|
||||
).toBe(true);
|
||||
expect(packFS.fileExists("otherfolder/lib.qll")).toBe(true);
|
||||
expect(packFS.fileExists("not-in-pack.ql")).toBe(false);
|
||||
|
||||
// the compiled pack
|
||||
verifyQlPack(
|
||||
"subfolder/in-pack.ql",
|
||||
packFS.fileContents("codeql-pack.yml"),
|
||||
"0.0.0",
|
||||
);
|
||||
|
||||
// should have generated a correct qlpack file
|
||||
const qlpackContents: any = load(
|
||||
packFS.fileContents("codeql-pack.yml").toString("utf-8"),
|
||||
);
|
||||
expect(qlpackContents.name).toBe("codeql-remote/query");
|
||||
expect(qlpackContents.version).toBe("0.0.0");
|
||||
expect(qlpackContents.dependencies?.["codeql/javascript-all"]).toBe("*");
|
||||
|
||||
const libraryDir = ".codeql/libraries/codeql";
|
||||
const packNames = packFS.directoryContents(libraryDir).sort();
|
||||
|
||||
// check dependencies.
|
||||
expect(packNames).toContain("javascript-all");
|
||||
});
|
||||
|
||||
it("should cancel a run before uploading", async () => {
|
||||
const fileUri = getFile("data-remote-no-qlpack/in-pack.ql");
|
||||
|
||||
const promise = remoteQueriesManager.runRemoteQuery(
|
||||
fileUri,
|
||||
progress,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
cancellationTokenSource.cancel();
|
||||
|
||||
await expect(promise).rejects.toThrow(UserCancellationException);
|
||||
});
|
||||
});
|
||||
|
||||
function verifyQlPack(
|
||||
queryPath: string,
|
||||
contents: Buffer,
|
||||
packVersion: string,
|
||||
) {
|
||||
const qlPack = load(contents.toString("utf-8")) as QlPack;
|
||||
|
||||
// don't check the build metadata since it is variable
|
||||
delete (qlPack as any).buildMetadata;
|
||||
|
||||
expect(qlPack).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "codeql-remote/query",
|
||||
version: packVersion,
|
||||
dependencies: {
|
||||
"codeql/javascript-all": "*",
|
||||
},
|
||||
defaultSuite: [
|
||||
{
|
||||
description: "Query suite for variant analysis",
|
||||
},
|
||||
{
|
||||
query: queryPath,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
// v2.11.6 and later set this to false.
|
||||
// Earlier versions don't set it at all.
|
||||
expect(qlPack.library).toBeFalsy();
|
||||
}
|
||||
|
||||
function getFile(file: string): Uri {
|
||||
return Uri.file(join(baseDir, file));
|
||||
}
|
||||
});
|
||||
@@ -13,6 +13,10 @@ const config = {
|
||||
"--extensions-dir=" + path.join(rootDir, ".vscode-test", "extensions"),
|
||||
],
|
||||
extensionDevelopmentPath: rootDir,
|
||||
// Hide VSCode stdout, but show console.log
|
||||
filterOutput: true,
|
||||
// Hide information about VSCode exit code and download
|
||||
quiet: true,
|
||||
};
|
||||
|
||||
if (process.env.VSCODE_INSPECTOR_OPTIONS) {
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import {
|
||||
cancelRemoteQuery,
|
||||
cancelVariantAnalysis,
|
||||
getRepositoriesMetadata,
|
||||
} from "../../../../../src/remote-queries/gh-api/gh-actions-api-client";
|
||||
import { RemoteQuery } from "../../../../../src/remote-queries/remote-query";
|
||||
import { createMockVariantAnalysis } from "../../../../factories/remote-queries/shared/variant-analysis";
|
||||
import { VariantAnalysis } from "../../../../../src/remote-queries/shared/variant-analysis";
|
||||
import {
|
||||
testCredentialsWithStub,
|
||||
testCredentialsWithRealOctokit,
|
||||
} from "../../../../factories/authentication";
|
||||
|
||||
jest.setTimeout(10000);
|
||||
|
||||
describe("gh-actions-api-client mock responses", () => {
|
||||
const mockRequest = jest.fn();
|
||||
const mockCredentials = testCredentialsWithStub(mockRequest);
|
||||
|
||||
describe("cancelRemoteQuery", () => {
|
||||
it("should cancel a remote query", async () => {
|
||||
mockRequest.mockReturnValue({ status: 202 });
|
||||
await cancelRemoteQuery(mockCredentials, createMockRemoteQuery());
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
"POST /repos/github/codeql/actions/runs/123/cancel",
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail to cancel a remote query", async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
status: 409,
|
||||
data: { message: "Uh oh!" },
|
||||
});
|
||||
|
||||
await expect(
|
||||
cancelRemoteQuery(mockCredentials, createMockRemoteQuery()),
|
||||
).rejects.toThrow(/Error cancelling variant analysis: 409 Uh oh!/);
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
"POST /repos/github/codeql/actions/runs/123/cancel",
|
||||
);
|
||||
});
|
||||
|
||||
function createMockRemoteQuery(): RemoteQuery {
|
||||
return {
|
||||
actionsWorkflowRunId: 123,
|
||||
controllerRepository: {
|
||||
owner: "github",
|
||||
name: "codeql",
|
||||
},
|
||||
} as unknown as RemoteQuery;
|
||||
}
|
||||
});
|
||||
|
||||
describe("cancelVariantAnalysis", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
beforeAll(() => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
});
|
||||
|
||||
it("should cancel a variant analysis", async () => {
|
||||
mockRequest.mockResolvedValue({ status: 202 });
|
||||
await cancelVariantAnalysis(mockCredentials, variantAnalysis);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
`POST /repos/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}/cancel`,
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail to cancel a variant analysis", async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
status: 409,
|
||||
data: { message: "Uh oh!" },
|
||||
});
|
||||
|
||||
await expect(
|
||||
cancelVariantAnalysis(mockCredentials, variantAnalysis),
|
||||
).rejects.toThrow(/Error cancelling variant analysis: 409 Uh oh!/);
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
`POST /repos/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}/cancel`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("gh-actions-api-client real responses", () => {
|
||||
it("should get the stargazers for repos", async () => {
|
||||
if (skip()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const credentials = testCredentialsWithRealOctokit(
|
||||
process.env.VSCODE_CODEQL_GITHUB_TOKEN!,
|
||||
);
|
||||
const stargazers = await getRepositoriesMetadata(
|
||||
credentials,
|
||||
[
|
||||
"github/codeql",
|
||||
"github/vscode-codeql",
|
||||
"rails/rails",
|
||||
"angular/angular",
|
||||
"github/hucairz", // This one should not be in the list
|
||||
],
|
||||
// choose a page size that is small enough to ensure we make multiple requests
|
||||
2,
|
||||
);
|
||||
|
||||
const stargazersKeys = Object.keys(stargazers).sort();
|
||||
expect(stargazersKeys).toEqual([
|
||||
"angular/angular",
|
||||
"github/codeql",
|
||||
"github/vscode-codeql",
|
||||
"rails/rails",
|
||||
]);
|
||||
});
|
||||
|
||||
function skip() {
|
||||
if (!process.env.VSCODE_CODEQL_GITHUB_TOKEN) {
|
||||
if (process.env.CI) {
|
||||
throw new Error(
|
||||
"The VSCODE_CODEQL_GITHUB_TOKEN must be set to a valid GITHUB token on CI",
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"Skipping gh-actions-api-client real responses tests. To run these tests, set the value VSCODE_CODEQL_GITHUB_TOKEN to a GitHub token.",
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -1,272 +0,0 @@
|
||||
import { EOL } from "os";
|
||||
import { parseResponse } from "../../../../src/remote-queries/remote-queries-api";
|
||||
import { Repository } from "../../../../src/remote-queries/shared/repository";
|
||||
|
||||
describe("parseResponse", () => {
|
||||
const controllerRepository: Repository = {
|
||||
id: 123,
|
||||
fullName: "org/name",
|
||||
private: true,
|
||||
};
|
||||
|
||||
it("should parse a successful response", () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b", "c/d"],
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b, c/d",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse a response with invalid repos", () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b", "c/d"],
|
||||
errors: {
|
||||
invalid_repositories: ["e/f", "g/h"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
"",
|
||||
"Some repositories could not be scheduled. See extension log for details.",
|
||||
].join(EOL),
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b, c/d",
|
||||
"",
|
||||
"Some repositories could not be scheduled.",
|
||||
"",
|
||||
"2 repositories invalid and could not be found:",
|
||||
"e/f, g/h",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse a response with repos w/o databases", () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b", "c/d"],
|
||||
errors: {
|
||||
repositories_without_database: ["e/f", "g/h"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
"",
|
||||
"Some repositories could not be scheduled. See extension log for details.",
|
||||
].join(EOL),
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b, c/d",
|
||||
"",
|
||||
"Some repositories could not be scheduled.",
|
||||
"",
|
||||
"2 repositories did not have a CodeQL database available:",
|
||||
"e/f, g/h",
|
||||
"For each public repository that has not yet been added to the database service, we will try to create a database next time the store is updated.",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse a response with private repos", () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b", "c/d"],
|
||||
errors: {
|
||||
private_repositories: ["e/f", "g/h"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
"",
|
||||
"Some repositories could not be scheduled. See extension log for details.",
|
||||
].join(EOL),
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b, c/d",
|
||||
"",
|
||||
"Some repositories could not be scheduled.",
|
||||
"",
|
||||
"2 repositories not public:",
|
||||
"e/f, g/h",
|
||||
"When using a public controller repository, only public repositories can be queried.",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse a response with cutoff repos and cutoff repos count", () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b", "c/d"],
|
||||
errors: {
|
||||
cutoff_repositories: ["e/f", "g/h"],
|
||||
cutoff_repositories_count: 2,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
"",
|
||||
"Some repositories could not be scheduled. See extension log for details.",
|
||||
].join(EOL),
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b, c/d",
|
||||
"",
|
||||
"Some repositories could not be scheduled.",
|
||||
"",
|
||||
"2 repositories over the limit for a single request:",
|
||||
"e/f, g/h",
|
||||
"Repositories were selected based on how recently they had been updated.",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse a response with cutoff repos count but not cutoff repos", () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b", "c/d"],
|
||||
errors: {
|
||||
cutoff_repositories_count: 2,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
"",
|
||||
"Some repositories could not be scheduled. See extension log for details.",
|
||||
].join(EOL),
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b, c/d",
|
||||
"",
|
||||
"Some repositories could not be scheduled.",
|
||||
"",
|
||||
"2 repositories over the limit for a single request.",
|
||||
"Repositories were selected based on how recently they had been updated.",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse a response with invalid repos and repos w/o databases", () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b", "c/d"],
|
||||
errors: {
|
||||
invalid_repositories: ["e/f", "g/h"],
|
||||
repositories_without_database: ["i/j", "k/l"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
"",
|
||||
"Some repositories could not be scheduled. See extension log for details.",
|
||||
].join(EOL),
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 2 repositories. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b, c/d",
|
||||
"",
|
||||
"Some repositories could not be scheduled.",
|
||||
"",
|
||||
"2 repositories invalid and could not be found:",
|
||||
"e/f, g/h",
|
||||
"",
|
||||
"2 repositories did not have a CodeQL database available:",
|
||||
"i/j, k/l",
|
||||
"For each public repository that has not yet been added to the database service, we will try to create a database next time the store is updated.",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse a response with one repo of each category, and not pluralize "repositories"', () => {
|
||||
const result = parseResponse(controllerRepository, {
|
||||
workflow_run_id: 123,
|
||||
repositories_queried: ["a/b"],
|
||||
errors: {
|
||||
private_repositories: ["e/f"],
|
||||
cutoff_repositories: ["i/j"],
|
||||
cutoff_repositories_count: 1,
|
||||
invalid_repositories: ["m/n"],
|
||||
repositories_without_database: ["q/r"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.popupMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 1 repository. [Click here to see the progress](https://github.com/org/name/actions/runs/123).",
|
||||
"",
|
||||
"Some repositories could not be scheduled. See extension log for details.",
|
||||
].join(EOL),
|
||||
);
|
||||
expect(result.logMessage).toBe(
|
||||
[
|
||||
"Successfully scheduled runs on 1 repository. See https://github.com/org/name/actions/runs/123.",
|
||||
"",
|
||||
"Repositories queried:",
|
||||
"a/b",
|
||||
"",
|
||||
"Some repositories could not be scheduled.",
|
||||
"",
|
||||
"1 repository invalid and could not be found:",
|
||||
"m/n",
|
||||
"",
|
||||
"1 repository did not have a CodeQL database available:",
|
||||
"q/r",
|
||||
"For each public repository that has not yet been added to the database service, we will try to create a database next time the store is updated.",
|
||||
"",
|
||||
"1 repository not public:",
|
||||
"e/f",
|
||||
"When using a public controller repository, only public repositories can be queried.",
|
||||
"",
|
||||
"1 repository over the limit for a single request:",
|
||||
"i/j",
|
||||
"Repositories were selected based on how recently they had been updated.",
|
||||
].join(EOL),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user