Merge pull request #2124 from github/koesie10/move-tests
Move variant analysis tests out from the CLI integration test suite
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
afterAllAction,
|
||||
beforeAllAction,
|
||||
beforeEachAction,
|
||||
} from "../jest.activated-extension.setup";
|
||||
@@ -10,3 +11,7 @@ beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
await beforeEachAction();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await afterAllAction();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,910 @@
|
||||
import {
|
||||
CancellationTokenSource,
|
||||
commands,
|
||||
env,
|
||||
extensions,
|
||||
TextDocument,
|
||||
TextEditor,
|
||||
Uri,
|
||||
window,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { CodeQLExtensionInterface } from "../../../../src/extension";
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client";
|
||||
import * as ghActionsApiClient from "../../../../src/variant-analysis/gh-api/gh-actions-api-client";
|
||||
import * as fs from "fs-extra";
|
||||
import { join } from "path";
|
||||
import { Readable } from "stream";
|
||||
import * as fetchModule from "node-fetch";
|
||||
import { Response } from "node-fetch";
|
||||
|
||||
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
import { storagePath } from "../../global.helper";
|
||||
import { VariantAnalysisResultsManager } from "../../../../src/variant-analysis/variant-analysis-results-manager";
|
||||
import { createMockVariantAnalysis } from "../../../factories/variant-analysis/shared/variant-analysis";
|
||||
import * as VariantAnalysisModule from "../../../../src/variant-analysis/shared/variant-analysis";
|
||||
import {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisScannedRepository,
|
||||
VariantAnalysisScannedRepositoryDownloadStatus,
|
||||
VariantAnalysisScannedRepositoryState,
|
||||
VariantAnalysisStatus,
|
||||
} from "../../../../src/variant-analysis/shared/variant-analysis";
|
||||
import {
|
||||
createMockScannedRepo,
|
||||
createMockScannedRepos,
|
||||
} from "../../../factories/variant-analysis/shared/scanned-repositories";
|
||||
import { createTimestampFile } from "../../../../src/helpers";
|
||||
import { createMockVariantAnalysisRepoTask } from "../../../factories/variant-analysis/gh-api/variant-analysis-repo-task";
|
||||
import { VariantAnalysisRepoTask } from "../../../../src/variant-analysis/gh-api/variant-analysis";
|
||||
import {
|
||||
defaultFilterSortState,
|
||||
SortKey,
|
||||
} from "../../../../src/pure/variant-analysis-filter-sort";
|
||||
import { DbManager } from "../../../../src/databases/db-manager";
|
||||
import { App } from "../../../../src/common/app";
|
||||
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
|
||||
import { DbConfigStore } from "../../../../src/databases/config/db-config-store";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
// up to 3 minutes per test
|
||||
jest.setTimeout(3 * 60 * 1000);
|
||||
|
||||
describe("Variant Analysis Manager", () => {
|
||||
let app: App;
|
||||
let cancellationTokenSource: CancellationTokenSource;
|
||||
let variantAnalysisManager: VariantAnalysisManager;
|
||||
let variantAnalysisResultsManager: VariantAnalysisResultsManager;
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let scannedRepos: VariantAnalysisScannedRepository[];
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(extLogger, "log").mockResolvedValue(undefined);
|
||||
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
scannedRepos = createMockScannedRepos();
|
||||
variantAnalysis = createMockVariantAnalysis({
|
||||
status: VariantAnalysisStatus.InProgress,
|
||||
scannedRepos,
|
||||
});
|
||||
|
||||
const extension = await extensions
|
||||
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
|
||||
"GitHub.vscode-codeql",
|
||||
)!
|
||||
.activate();
|
||||
const cli = mockedObject<CodeQLCliServer>({});
|
||||
app = new ExtensionApp(extension.ctx);
|
||||
const dbManager = new DbManager(app, new DbConfigStore(app));
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
variantAnalysisManager = new VariantAnalysisManager(
|
||||
extension.ctx,
|
||||
app,
|
||||
cli,
|
||||
storagePath,
|
||||
variantAnalysisResultsManager,
|
||||
dbManager,
|
||||
);
|
||||
});
|
||||
|
||||
describe("rehydrateVariantAnalysis", () => {
|
||||
const variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
describe("when the directory does not exist", () => {
|
||||
it("should fire the removed event if the file does not exist", async () => {
|
||||
const stub = jest.fn();
|
||||
variantAnalysisManager.onVariantAnalysisRemoved(stub);
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(stub).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the directory exists", () => {
|
||||
beforeEach(async () => {
|
||||
await fs.ensureDir(join(storagePath, variantAnalysis.id.toString()));
|
||||
});
|
||||
|
||||
it("should store the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(
|
||||
await variantAnalysisManager.getVariantAnalysis(variantAnalysis.id),
|
||||
).toEqual(variantAnalysis);
|
||||
});
|
||||
|
||||
it("should not error if the repo states file does not exist", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(
|
||||
await variantAnalysisManager.getRepoStates(variantAnalysis.id),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("should read in the repo states if it exists", async () => {
|
||||
await fs.writeJson(
|
||||
join(storagePath, variantAnalysis.id.toString(), "repo_states.json"),
|
||||
{
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(
|
||||
await variantAnalysisManager.getRepoStates(variantAnalysis.id),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
{
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("autoDownloadVariantAnalysisResult", () => {
|
||||
let getVariantAnalysisRepoStub: jest.SpiedFunction<
|
||||
typeof ghApiClient.getVariantAnalysisRepo
|
||||
>;
|
||||
let getVariantAnalysisRepoResultStub: jest.SpiedFunction<
|
||||
typeof fetchModule.default
|
||||
>;
|
||||
|
||||
let repoStatesPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
getVariantAnalysisRepoStub = jest.spyOn(
|
||||
ghApiClient,
|
||||
"getVariantAnalysisRepo",
|
||||
);
|
||||
getVariantAnalysisRepoResultStub = jest.spyOn(fetchModule, "default");
|
||||
|
||||
repoStatesPath = join(
|
||||
storagePath,
|
||||
variantAnalysis.id.toString(),
|
||||
"repo_states.json",
|
||||
);
|
||||
});
|
||||
|
||||
describe("when the artifact_url is missing", () => {
|
||||
beforeEach(async () => {
|
||||
const dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||
delete dummyRepoTask.artifact_url;
|
||||
|
||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||
});
|
||||
|
||||
it("should not try to download the result", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the artifact_url is present", () => {
|
||||
let dummyRepoTask: VariantAnalysisRepoTask;
|
||||
|
||||
beforeEach(async () => {
|
||||
dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||
|
||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||
|
||||
const sourceFilePath = join(
|
||||
__dirname,
|
||||
"data/variant-analysis-results.zip",
|
||||
);
|
||||
const fileContents = fs.readFileSync(sourceFilePath);
|
||||
const response = new Response(Readable.from(fileContents));
|
||||
response.size = fileContents.length;
|
||||
getVariantAnalysisRepoResultStub.mockResolvedValue(response);
|
||||
});
|
||||
|
||||
it("should return early if variant analysis is cancelled", async () => {
|
||||
cancellationTokenSource.cancel();
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should fetch a repo task", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should fetch a repo result", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should skip the download if the repository has already been downloaded", async () => {
|
||||
// First, do a download so it is downloaded. This avoids having to mock the repo states.
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
getVariantAnalysisRepoStub.mockClear();
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should write the repo state when the download is successful", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should not write the repo state when the download fails", async () => {
|
||||
getVariantAnalysisRepoResultStub.mockRejectedValue(
|
||||
new Error("Failed to download"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("should have a failed repo state when the repo task API fails", async () => {
|
||||
getVariantAnalysisRepoStub.mockRejectedValueOnce(
|
||||
new Error("Failed to download"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false);
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[1],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
},
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should have a failed repo state when the download fails", async () => {
|
||||
getVariantAnalysisRepoResultStub.mockRejectedValueOnce(
|
||||
new Error("Failed to download"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false);
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[1],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
},
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the repo state correctly", async () => {
|
||||
await mockRepoStates({
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
[scannedRepos[2].repository.id]: {
|
||||
repositoryId: scannedRepos[2].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
});
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
[scannedRepos[2].repository.id]: {
|
||||
repositoryId: scannedRepos[2].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
async function mockRepoStates(
|
||||
repoStates: Record<number, VariantAnalysisScannedRepositoryState>,
|
||||
) {
|
||||
await fs.outputJson(repoStatesPath, repoStates);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("enqueueDownload", () => {
|
||||
it("should pop download tasks off the queue", async () => {
|
||||
const getResultsSpy = jest
|
||||
.spyOn(variantAnalysisManager, "autoDownloadVariantAnalysisResult")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await variantAnalysisManager.enqueueDownload(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
await variantAnalysisManager.enqueueDownload(
|
||||
scannedRepos[1],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
await variantAnalysisManager.enqueueDownload(
|
||||
scannedRepos[2],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(variantAnalysisManager.downloadsQueueSize()).toBe(0);
|
||||
expect(getResultsSpy).toBeCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeVariantAnalysis", () => {
|
||||
let removeAnalysisResultsStub: jest.SpiedFunction<
|
||||
typeof variantAnalysisResultsManager.removeAnalysisResults
|
||||
>;
|
||||
let dummyVariantAnalysis: VariantAnalysis;
|
||||
|
||||
beforeEach(async () => {
|
||||
dummyVariantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
removeAnalysisResultsStub = jest
|
||||
.spyOn(variantAnalysisResultsManager, "removeAnalysisResults")
|
||||
.mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
it("should remove variant analysis", async () => {
|
||||
await fs.ensureDir(join(storagePath, dummyVariantAnalysis.id.toString()));
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
dummyVariantAnalysis,
|
||||
);
|
||||
expect(variantAnalysisManager.variantAnalysesSize).toBe(1);
|
||||
|
||||
await variantAnalysisManager.removeVariantAnalysis(dummyVariantAnalysis);
|
||||
|
||||
expect(removeAnalysisResultsStub).toBeCalledTimes(1);
|
||||
expect(variantAnalysisManager.variantAnalysesSize).toBe(0);
|
||||
|
||||
await expect(
|
||||
fs.pathExists(join(storagePath, dummyVariantAnalysis.id.toString())),
|
||||
).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rehydrateVariantAnalysis", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
const variantAnalysisRemovedSpy = jest.fn();
|
||||
let executeCommandSpy: jest.SpiedFunction<typeof commands.executeCommand>;
|
||||
|
||||
beforeEach(() => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisManager.onVariantAnalysisRemoved(
|
||||
variantAnalysisRemovedSpy,
|
||||
);
|
||||
|
||||
executeCommandSpy = jest
|
||||
.spyOn(commands, "executeCommand")
|
||||
.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
describe("when variant analysis record doesn't exist", () => {
|
||||
it("should remove the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
expect(variantAnalysisRemovedSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not trigger a monitoring command", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
expect(executeCommandSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when variant analysis record does exist", () => {
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
describe("when the variant analysis is not complete", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(VariantAnalysisModule, "isVariantAnalysisComplete")
|
||||
.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
it("should not remove the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(variantAnalysisRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should trigger a monitoring command", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(executeCommandSpy).toHaveBeenCalledWith(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the variant analysis is complete", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(VariantAnalysisModule, "isVariantAnalysisComplete")
|
||||
.mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it("should not remove the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(variantAnalysisRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not trigger a monitoring command", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(executeCommandSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancelVariantAnalysis", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let mockCancelVariantAnalysis: jest.SpiedFunction<
|
||||
typeof ghActionsApiClient.cancelVariantAnalysis
|
||||
>;
|
||||
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
mockCancelVariantAnalysis = jest
|
||||
.spyOn(ghActionsApiClient, "cancelVariantAnalysis")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
it("should return early if the variant analysis is not found", async () => {
|
||||
try {
|
||||
await variantAnalysisManager.cancelVariantAnalysis(
|
||||
variantAnalysis.id + 100,
|
||||
);
|
||||
} catch (error: any) {
|
||||
expect(error.message).toBe(
|
||||
`No variant analysis with id: ${variantAnalysis.id + 100}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should return early if the variant analysis does not have an actions workflow run id", async () => {
|
||||
await variantAnalysisManager.onVariantAnalysisUpdated({
|
||||
...variantAnalysis,
|
||||
actionsWorkflowRunId: undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id);
|
||||
} catch (error: any) {
|
||||
expect(error.message).toBe(
|
||||
`No workflow run id for variant analysis with id: ${variantAnalysis.id}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should return cancel if valid", async () => {
|
||||
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id);
|
||||
|
||||
expect(mockCancelVariantAnalysis).toBeCalledWith(
|
||||
app.credentials,
|
||||
variantAnalysis,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("copyRepoListToClipboard", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
const writeTextStub = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
jest.spyOn(env, "clipboard", "get").mockReturnValue({
|
||||
readText: jest.fn(),
|
||||
writeText: writeTextStub,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
describe("when the variant analysis does not have any repositories", () => {
|
||||
beforeEach(async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||
...variantAnalysis,
|
||||
scannedRepos: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not copy any text", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
expect(writeTextStub).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the variant analysis does not have any repositories with results", () => {
|
||||
beforeEach(async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||
...variantAnalysis,
|
||||
scannedRepos: [
|
||||
{
|
||||
...createMockScannedRepo(),
|
||||
resultCount: 0,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo(),
|
||||
resultCount: undefined,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not copy any text", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
expect(writeTextStub).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the variant analysis has repositories with results", () => {
|
||||
const scannedRepos = [
|
||||
{
|
||||
...createMockScannedRepo("pear"),
|
||||
resultCount: 100,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("apple"),
|
||||
resultCount: 0,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("citrus"),
|
||||
resultCount: 200,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("sky"),
|
||||
resultCount: undefined,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("banana"),
|
||||
resultCount: 5,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||
...variantAnalysis,
|
||||
scannedRepos,
|
||||
});
|
||||
});
|
||||
|
||||
it("should copy text", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
expect(writeTextStub).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should be valid JSON when put in object", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[4].repository.fullName,
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the sort key", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
scannedRepos[4].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the search value", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
searchValue: "ban",
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [scannedRepos[4].repository.fullName],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("openQueryText", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
let showTextDocumentSpy: jest.SpiedFunction<typeof window.showTextDocument>;
|
||||
let openTextDocumentSpy: jest.SpiedFunction<
|
||||
typeof workspace.openTextDocument
|
||||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
showTextDocumentSpy = jest
|
||||
.spyOn(window, "showTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextEditor>({}));
|
||||
openTextDocumentSpy = jest
|
||||
.spyOn(workspace, "openTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextDocument>({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
it("opens the query text", async () => {
|
||||
await variantAnalysisManager.openQueryText(variantAnalysis.id);
|
||||
|
||||
expect(openTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const uri: Uri = openTextDocumentSpy.mock.calls[0][0] as Uri;
|
||||
expect(uri.scheme).toEqual("codeql-variant-analysis");
|
||||
expect(uri.path).toEqual(variantAnalysis.query.filePath);
|
||||
const params = new URLSearchParams(uri.query);
|
||||
expect(Array.from(params.keys())).toEqual(["variantAnalysisId"]);
|
||||
expect(params.get("variantAnalysisId")).toEqual(
|
||||
variantAnalysis.id.toString(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("openQueryFile", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
let showTextDocumentSpy: jest.SpiedFunction<typeof window.showTextDocument>;
|
||||
let openTextDocumentSpy: jest.SpiedFunction<
|
||||
typeof workspace.openTextDocument
|
||||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
showTextDocumentSpy = jest
|
||||
.spyOn(window, "showTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextEditor>({}));
|
||||
openTextDocumentSpy = jest
|
||||
.spyOn(workspace, "openTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextDocument>({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
it("opens the query file", async () => {
|
||||
await variantAnalysisManager.openQueryFile(variantAnalysis.id);
|
||||
|
||||
expect(showTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
expect(openTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const filename: string = openTextDocumentSpy.mock.calls[0][0] as string;
|
||||
expect(filename).toEqual(variantAnalysis.query.filePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,9 @@
|
||||
import { extensions } from "vscode";
|
||||
import { CodeQLExtensionInterface } from "../../../../src/extension";
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import * as fs from "fs-extra";
|
||||
import { join, resolve } from "path";
|
||||
import { Readable } from "stream";
|
||||
import { Response, RequestInfo, RequestInit } from "node-fetch";
|
||||
import * as fetchModule from "node-fetch";
|
||||
import { RequestInfo, RequestInit, Response } from "node-fetch";
|
||||
|
||||
import { VariantAnalysisResultsManager } from "../../../../src/variant-analysis/variant-analysis-results-manager";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
@@ -16,38 +14,32 @@ import {
|
||||
VariantAnalysisRepositoryTask,
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
} from "../../../../src/variant-analysis/shared/variant-analysis";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
jest.setTimeout(10_000);
|
||||
|
||||
describe(VariantAnalysisResultsManager.name, () => {
|
||||
let cli: CodeQLCliServer;
|
||||
let variantAnalysisId: number;
|
||||
let variantAnalysisResultsManager: VariantAnalysisResultsManager;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysisId = faker.datatype.number();
|
||||
|
||||
const extension = await extensions
|
||||
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
|
||||
"GitHub.vscode-codeql",
|
||||
)!
|
||||
.activate();
|
||||
cli = extension.cliServer;
|
||||
const cli = mockedObject<CodeQLCliServer>({});
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
});
|
||||
|
||||
describe("download", () => {
|
||||
let dummyRepoTask: VariantAnalysisRepositoryTask;
|
||||
let variantAnalysisStoragePath: string;
|
||||
let repoTaskStorageDirectory: string;
|
||||
let variantAnalysisResultsManager: VariantAnalysisResultsManager;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(extLogger, "log").mockResolvedValue(undefined);
|
||||
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
|
||||
dummyRepoTask = createMockVariantAnalysisRepositoryTask();
|
||||
|
||||
variantAnalysisStoragePath = join(
|
||||
@@ -103,7 +95,7 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
beforeEach(async () => {
|
||||
const sourceFilePath = join(
|
||||
__dirname,
|
||||
"../data/variant-analysis-results.zip",
|
||||
"data/variant-analysis-results.zip",
|
||||
);
|
||||
fileContents = fs.readFileSync(sourceFilePath);
|
||||
|
||||
@@ -222,17 +214,12 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
let dummyRepoTask: VariantAnalysisRepositoryTask;
|
||||
let variantAnalysisStoragePath: string;
|
||||
let repoTaskStorageDirectory: string;
|
||||
let variantAnalysisResultsManager: VariantAnalysisResultsManager;
|
||||
let onResultLoadedSpy: jest.Mock<
|
||||
void,
|
||||
[VariantAnalysisScannedRepositoryResult]
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
onResultLoadedSpy = jest.fn();
|
||||
variantAnalysisResultsManager.onResultLoaded(onResultLoadedSpy);
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { workspace } from "vscode";
|
||||
|
||||
import {
|
||||
afterAllAction,
|
||||
beforeAllAction,
|
||||
beforeEachAction,
|
||||
} from "../jest.activated-extension.setup";
|
||||
import * as tmp from "tmp";
|
||||
import {
|
||||
createWriteStream,
|
||||
existsSync,
|
||||
mkdirpSync,
|
||||
realpathSync,
|
||||
} from "fs-extra";
|
||||
import { createWriteStream, existsSync, mkdirpSync } from "fs-extra";
|
||||
import { dirname } from "path";
|
||||
import { DB_URL, dbLoc, setStoragePath, storagePath } from "../global.helper";
|
||||
import { DB_URL, dbLoc } from "../global.helper";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
// create an extension storage location
|
||||
let removeStorage: tmp.DirResult["removeCallback"] | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
// ensure the test database is downloaded
|
||||
mkdirpSync(dirname(dbLoc));
|
||||
@@ -38,18 +30,6 @@ beforeAll(async () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Create the temp directory to be used as extension local storage.
|
||||
const dir = tmp.dirSync();
|
||||
let storagePath = realpathSync(dir.name);
|
||||
if (storagePath.substring(0, 2).match(/[A-Z]:/)) {
|
||||
storagePath =
|
||||
storagePath.substring(0, 1).toLocaleLowerCase() +
|
||||
storagePath.substring(1);
|
||||
}
|
||||
setStoragePath(storagePath);
|
||||
|
||||
removeStorage = dir.removeCallback;
|
||||
|
||||
await beforeAllAction();
|
||||
});
|
||||
|
||||
@@ -76,14 +56,6 @@ beforeAll(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// ensure extension is cleaned up.
|
||||
afterAll(async () => {
|
||||
// ensure temp directory is cleaned up.
|
||||
try {
|
||||
removeStorage?.();
|
||||
} catch (e) {
|
||||
// we are exiting anyway so don't worry about it.
|
||||
// most likely the directory this is a test on Windows and some files are locked.
|
||||
console.warn(`Failed to remove storage directory '${storagePath}': ${e}`);
|
||||
}
|
||||
await afterAllAction();
|
||||
});
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import {
|
||||
CancellationTokenSource,
|
||||
commands,
|
||||
env,
|
||||
extensions,
|
||||
QuickPickItem,
|
||||
TextDocument,
|
||||
TextEditor,
|
||||
Uri,
|
||||
window,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { CodeQLExtensionInterface } from "../../../../src/extension";
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import { setRemoteControllerRepo } from "../../../../src/config";
|
||||
import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client";
|
||||
import * as ghActionsApiClient from "../../../../src/variant-analysis/gh-api/gh-actions-api-client";
|
||||
import * as fs from "fs-extra";
|
||||
import { join } from "path";
|
||||
import { Readable } from "stream";
|
||||
import { Response } from "node-fetch";
|
||||
import * as fetchModule from "node-fetch";
|
||||
|
||||
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
@@ -29,71 +20,37 @@ import {
|
||||
storagePath,
|
||||
} from "../../global.helper";
|
||||
import { VariantAnalysisResultsManager } from "../../../../src/variant-analysis/variant-analysis-results-manager";
|
||||
import { createMockVariantAnalysis } from "../../../factories/variant-analysis/shared/variant-analysis";
|
||||
import * as VariantAnalysisModule from "../../../../src/variant-analysis/shared/variant-analysis";
|
||||
import {
|
||||
createMockScannedRepo,
|
||||
createMockScannedRepos,
|
||||
} from "../../../factories/variant-analysis/shared/scanned-repositories";
|
||||
import {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisScannedRepository,
|
||||
VariantAnalysisScannedRepositoryDownloadStatus,
|
||||
VariantAnalysisScannedRepositoryState,
|
||||
VariantAnalysisStatus,
|
||||
} from "../../../../src/variant-analysis/shared/variant-analysis";
|
||||
import { createTimestampFile } from "../../../../src/helpers";
|
||||
import { createMockVariantAnalysisRepoTask } from "../../../factories/variant-analysis/gh-api/variant-analysis-repo-task";
|
||||
import {
|
||||
VariantAnalysis as VariantAnalysisApiResponse,
|
||||
VariantAnalysisRepoTask,
|
||||
} from "../../../../src/variant-analysis/gh-api/variant-analysis";
|
||||
import { VariantAnalysisStatus } from "../../../../src/variant-analysis/shared/variant-analysis";
|
||||
import { VariantAnalysis as VariantAnalysisApiResponse } from "../../../../src/variant-analysis/gh-api/variant-analysis";
|
||||
import { createMockApiResponse } from "../../../factories/variant-analysis/gh-api/variant-analysis-api-response";
|
||||
import { UserCancellationException } from "../../../../src/commandRunner";
|
||||
import { Repository } from "../../../../src/variant-analysis/gh-api/repository";
|
||||
import {
|
||||
defaultFilterSortState,
|
||||
SortKey,
|
||||
} from "../../../../src/pure/variant-analysis-filter-sort";
|
||||
import { DbManager } from "../../../../src/databases/db-manager";
|
||||
import { App } from "../../../../src/common/app";
|
||||
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
|
||||
import { DbConfigStore } from "../../../../src/databases/config/db-config-store";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
// up to 3 minutes per test
|
||||
jest.setTimeout(3 * 60 * 1000);
|
||||
|
||||
describe("Variant Analysis Manager", () => {
|
||||
let cli: CodeQLCliServer;
|
||||
let app: App;
|
||||
let cancellationTokenSource: CancellationTokenSource;
|
||||
let variantAnalysisManager: VariantAnalysisManager;
|
||||
let variantAnalysisResultsManager: VariantAnalysisResultsManager;
|
||||
let dbManager: DbManager;
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let scannedRepos: VariantAnalysisScannedRepository[];
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(extLogger, "log").mockResolvedValue(undefined);
|
||||
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
scannedRepos = createMockScannedRepos();
|
||||
variantAnalysis = createMockVariantAnalysis({
|
||||
status: VariantAnalysisStatus.InProgress,
|
||||
scannedRepos,
|
||||
});
|
||||
|
||||
const extension = await extensions
|
||||
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
|
||||
"GitHub.vscode-codeql",
|
||||
)!
|
||||
.activate();
|
||||
cli = extension.cliServer;
|
||||
app = new ExtensionApp(extension.ctx);
|
||||
dbManager = new DbManager(app, new DbConfigStore(app));
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
const app = new ExtensionApp(extension.ctx);
|
||||
const dbManager = new DbManager(app, new DbConfigStore(app));
|
||||
const variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
@@ -246,819 +203,4 @@ describe("Variant Analysis Manager", () => {
|
||||
await expect(promise).rejects.toThrow(UserCancellationException);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rehydrateVariantAnalysis", () => {
|
||||
const variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
describe("when the directory does not exist", () => {
|
||||
it("should fire the removed event if the file does not exist", async () => {
|
||||
const stub = jest.fn();
|
||||
variantAnalysisManager.onVariantAnalysisRemoved(stub);
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(stub).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the directory exists", () => {
|
||||
beforeEach(async () => {
|
||||
await fs.ensureDir(join(storagePath, variantAnalysis.id.toString()));
|
||||
});
|
||||
|
||||
it("should store the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(
|
||||
await variantAnalysisManager.getVariantAnalysis(variantAnalysis.id),
|
||||
).toEqual(variantAnalysis);
|
||||
});
|
||||
|
||||
it("should not error if the repo states file does not exist", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(
|
||||
await variantAnalysisManager.getRepoStates(variantAnalysis.id),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("should read in the repo states if it exists", async () => {
|
||||
await fs.writeJson(
|
||||
join(storagePath, variantAnalysis.id.toString(), "repo_states.json"),
|
||||
{
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
expect(
|
||||
await variantAnalysisManager.getRepoStates(variantAnalysis.id),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
{
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("autoDownloadVariantAnalysisResult", () => {
|
||||
let getVariantAnalysisRepoStub: jest.SpiedFunction<
|
||||
typeof ghApiClient.getVariantAnalysisRepo
|
||||
>;
|
||||
let getVariantAnalysisRepoResultStub: jest.SpiedFunction<
|
||||
typeof fetchModule.default
|
||||
>;
|
||||
|
||||
let repoStatesPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
getVariantAnalysisRepoStub = jest.spyOn(
|
||||
ghApiClient,
|
||||
"getVariantAnalysisRepo",
|
||||
);
|
||||
getVariantAnalysisRepoResultStub = jest.spyOn(fetchModule, "default");
|
||||
|
||||
repoStatesPath = join(
|
||||
storagePath,
|
||||
variantAnalysis.id.toString(),
|
||||
"repo_states.json",
|
||||
);
|
||||
});
|
||||
|
||||
describe("when the artifact_url is missing", () => {
|
||||
beforeEach(async () => {
|
||||
const dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||
delete dummyRepoTask.artifact_url;
|
||||
|
||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||
});
|
||||
|
||||
it("should not try to download the result", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the artifact_url is present", () => {
|
||||
let dummyRepoTask: VariantAnalysisRepoTask;
|
||||
|
||||
beforeEach(async () => {
|
||||
dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||
|
||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||
|
||||
const sourceFilePath = join(
|
||||
__dirname,
|
||||
"../data/variant-analysis-results.zip",
|
||||
);
|
||||
const fileContents = fs.readFileSync(sourceFilePath);
|
||||
const response = new Response(Readable.from(fileContents));
|
||||
response.size = fileContents.length;
|
||||
getVariantAnalysisRepoResultStub.mockResolvedValue(response);
|
||||
});
|
||||
|
||||
it("should return early if variant analysis is cancelled", async () => {
|
||||
cancellationTokenSource.cancel();
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should fetch a repo task", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should fetch a repo result", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should skip the download if the repository has already been downloaded", async () => {
|
||||
// First, do a download so it is downloaded. This avoids having to mock the repo states.
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
getVariantAnalysisRepoStub.mockClear();
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should write the repo state when the download is successful", async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should not write the repo state when the download fails", async () => {
|
||||
getVariantAnalysisRepoResultStub.mockRejectedValue(
|
||||
new Error("Failed to download"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("should have a failed repo state when the repo task API fails", async () => {
|
||||
getVariantAnalysisRepoStub.mockRejectedValueOnce(
|
||||
new Error("Failed to download"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false);
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[1],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
},
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should have a failed repo state when the download fails", async () => {
|
||||
getVariantAnalysisRepoResultStub.mockRejectedValueOnce(
|
||||
new Error("Failed to download"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
|
||||
await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false);
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[1],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||
},
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the repo state correctly", async () => {
|
||||
await mockRepoStates({
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
[scannedRepos[2].repository.id]: {
|
||||
repositoryId: scannedRepos[2].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
});
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
await expect(fs.readJson(repoStatesPath)).resolves.toEqual({
|
||||
[scannedRepos[1].repository.id]: {
|
||||
repositoryId: scannedRepos[1].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
[scannedRepos[2].repository.id]: {
|
||||
repositoryId: scannedRepos[2].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||
},
|
||||
[scannedRepos[0].repository.id]: {
|
||||
repositoryId: scannedRepos[0].repository.id,
|
||||
downloadStatus:
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
async function mockRepoStates(
|
||||
repoStates: Record<number, VariantAnalysisScannedRepositoryState>,
|
||||
) {
|
||||
await fs.outputJson(repoStatesPath, repoStates);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("enqueueDownload", () => {
|
||||
it("should pop download tasks off the queue", async () => {
|
||||
const getResultsSpy = jest
|
||||
.spyOn(variantAnalysisManager, "autoDownloadVariantAnalysisResult")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await variantAnalysisManager.enqueueDownload(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
await variantAnalysisManager.enqueueDownload(
|
||||
scannedRepos[1],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
await variantAnalysisManager.enqueueDownload(
|
||||
scannedRepos[2],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token,
|
||||
);
|
||||
|
||||
expect(variantAnalysisManager.downloadsQueueSize()).toBe(0);
|
||||
expect(getResultsSpy).toBeCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeVariantAnalysis", () => {
|
||||
let removeAnalysisResultsStub: jest.SpiedFunction<
|
||||
typeof variantAnalysisResultsManager.removeAnalysisResults
|
||||
>;
|
||||
let dummyVariantAnalysis: VariantAnalysis;
|
||||
|
||||
beforeEach(async () => {
|
||||
dummyVariantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
removeAnalysisResultsStub = jest
|
||||
.spyOn(variantAnalysisResultsManager, "removeAnalysisResults")
|
||||
.mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
it("should remove variant analysis", async () => {
|
||||
await fs.ensureDir(join(storagePath, dummyVariantAnalysis.id.toString()));
|
||||
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
dummyVariantAnalysis,
|
||||
);
|
||||
expect(variantAnalysisManager.variantAnalysesSize).toBe(1);
|
||||
|
||||
await variantAnalysisManager.removeVariantAnalysis(dummyVariantAnalysis);
|
||||
|
||||
expect(removeAnalysisResultsStub).toBeCalledTimes(1);
|
||||
expect(variantAnalysisManager.variantAnalysesSize).toBe(0);
|
||||
|
||||
await expect(
|
||||
fs.pathExists(join(storagePath, dummyVariantAnalysis.id.toString())),
|
||||
).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rehydrateVariantAnalysis", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
const variantAnalysisRemovedSpy = jest.fn();
|
||||
let executeCommandSpy: jest.SpiedFunction<typeof commands.executeCommand>;
|
||||
|
||||
beforeEach(() => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisManager.onVariantAnalysisRemoved(
|
||||
variantAnalysisRemovedSpy,
|
||||
);
|
||||
|
||||
executeCommandSpy = jest
|
||||
.spyOn(commands, "executeCommand")
|
||||
.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
describe("when variant analysis record doesn't exist", () => {
|
||||
it("should remove the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
expect(variantAnalysisRemovedSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not trigger a monitoring command", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
expect(executeCommandSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when variant analysis record does exist", () => {
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
describe("when the variant analysis is not complete", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(VariantAnalysisModule, "isVariantAnalysisComplete")
|
||||
.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
it("should not remove the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(variantAnalysisRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should trigger a monitoring command", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(executeCommandSpy).toHaveBeenCalledWith(
|
||||
"codeQL.monitorVariantAnalysis",
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the variant analysis is complete", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(VariantAnalysisModule, "isVariantAnalysisComplete")
|
||||
.mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it("should not remove the variant analysis", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(variantAnalysisRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not trigger a monitoring command", async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
variantAnalysis,
|
||||
);
|
||||
expect(executeCommandSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancelVariantAnalysis", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let mockCancelVariantAnalysis: jest.SpiedFunction<
|
||||
typeof ghActionsApiClient.cancelVariantAnalysis
|
||||
>;
|
||||
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
mockCancelVariantAnalysis = jest
|
||||
.spyOn(ghActionsApiClient, "cancelVariantAnalysis")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
it("should return early if the variant analysis is not found", async () => {
|
||||
try {
|
||||
await variantAnalysisManager.cancelVariantAnalysis(
|
||||
variantAnalysis.id + 100,
|
||||
);
|
||||
} catch (error: any) {
|
||||
expect(error.message).toBe(
|
||||
`No variant analysis with id: ${variantAnalysis.id + 100}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should return early if the variant analysis does not have an actions workflow run id", async () => {
|
||||
await variantAnalysisManager.onVariantAnalysisUpdated({
|
||||
...variantAnalysis,
|
||||
actionsWorkflowRunId: undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id);
|
||||
} catch (error: any) {
|
||||
expect(error.message).toBe(
|
||||
`No workflow run id for variant analysis with id: ${variantAnalysis.id}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should return cancel if valid", async () => {
|
||||
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id);
|
||||
|
||||
expect(mockCancelVariantAnalysis).toBeCalledWith(
|
||||
app.credentials,
|
||||
variantAnalysis,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("copyRepoListToClipboard", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
const writeTextStub = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
jest.spyOn(env, "clipboard", "get").mockReturnValue({
|
||||
readText: jest.fn(),
|
||||
writeText: writeTextStub,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
describe("when the variant analysis does not have any repositories", () => {
|
||||
beforeEach(async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||
...variantAnalysis,
|
||||
scannedRepos: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not copy any text", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
expect(writeTextStub).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the variant analysis does not have any repositories with results", () => {
|
||||
beforeEach(async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||
...variantAnalysis,
|
||||
scannedRepos: [
|
||||
{
|
||||
...createMockScannedRepo(),
|
||||
resultCount: 0,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo(),
|
||||
resultCount: undefined,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not copy any text", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
expect(writeTextStub).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the variant analysis has repositories with results", () => {
|
||||
const scannedRepos = [
|
||||
{
|
||||
...createMockScannedRepo("pear"),
|
||||
resultCount: 100,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("apple"),
|
||||
resultCount: 0,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("citrus"),
|
||||
resultCount: 200,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("sky"),
|
||||
resultCount: undefined,
|
||||
},
|
||||
{
|
||||
...createMockScannedRepo("banana"),
|
||||
resultCount: 5,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(async () => {
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis({
|
||||
...variantAnalysis,
|
||||
scannedRepos,
|
||||
});
|
||||
});
|
||||
|
||||
it("should copy text", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
expect(writeTextStub).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should be valid JSON when put in object", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[4].repository.fullName,
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the sort key", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
scannedRepos[4].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the search value", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
searchValue: "ban",
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [scannedRepos[4].repository.fullName],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("openQueryText", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
let showTextDocumentSpy: jest.SpiedFunction<typeof window.showTextDocument>;
|
||||
let openTextDocumentSpy: jest.SpiedFunction<
|
||||
typeof workspace.openTextDocument
|
||||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
showTextDocumentSpy = jest
|
||||
.spyOn(window, "showTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextEditor>({}));
|
||||
openTextDocumentSpy = jest
|
||||
.spyOn(workspace, "openTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextDocument>({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
it("opens the query text", async () => {
|
||||
await variantAnalysisManager.openQueryText(variantAnalysis.id);
|
||||
|
||||
expect(openTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const uri: Uri = openTextDocumentSpy.mock.calls[0][0] as Uri;
|
||||
expect(uri.scheme).toEqual("codeql-variant-analysis");
|
||||
expect(uri.path).toEqual(variantAnalysis.query.filePath);
|
||||
const params = new URLSearchParams(uri.query);
|
||||
expect(Array.from(params.keys())).toEqual(["variantAnalysisId"]);
|
||||
expect(params.get("variantAnalysisId")).toEqual(
|
||||
variantAnalysis.id.toString(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("openQueryFile", () => {
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
let variantAnalysisStorageLocation: string;
|
||||
|
||||
let showTextDocumentSpy: jest.SpiedFunction<typeof window.showTextDocument>;
|
||||
let openTextDocumentSpy: jest.SpiedFunction<
|
||||
typeof workspace.openTextDocument
|
||||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
variantAnalysisStorageLocation =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
await createTimestampFile(variantAnalysisStorageLocation);
|
||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||
|
||||
showTextDocumentSpy = jest
|
||||
.spyOn(window, "showTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextEditor>({}));
|
||||
openTextDocumentSpy = jest
|
||||
.spyOn(workspace, "openTextDocument")
|
||||
.mockResolvedValue(mockedObject<TextDocument>({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||
});
|
||||
|
||||
it("opens the query file", async () => {
|
||||
await variantAnalysisManager.openQueryFile(variantAnalysis.id);
|
||||
|
||||
expect(showTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
expect(openTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const filename: string = openTextDocumentSpy.mock.calls[0][0] as string;
|
||||
expect(filename).toEqual(variantAnalysis.query.filePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { CUSTOM_CODEQL_PATH_SETTING } from "../../src/config";
|
||||
import { ConfigurationTarget, env, extensions } from "vscode";
|
||||
import { beforeEachAction as testConfigBeforeEachAction } from "./test-config";
|
||||
import * as tmp from "tmp";
|
||||
import { realpathSync } from "fs-extra";
|
||||
import { setStoragePath, storagePath } from "./global.helper";
|
||||
|
||||
jest.retryTimes(3, {
|
||||
logErrorsBeforeRetry: true,
|
||||
});
|
||||
|
||||
// create an extension storage location
|
||||
let removeStorage: tmp.DirResult["removeCallback"] | undefined;
|
||||
|
||||
export async function beforeAllAction() {
|
||||
// Set the CLI version here before activation to ensure we don't accidentally try to download a cli
|
||||
await testConfigBeforeEachAction();
|
||||
@@ -14,6 +20,18 @@ export async function beforeAllAction() {
|
||||
ConfigurationTarget.Workspace,
|
||||
);
|
||||
|
||||
// Create the temp directory to be used as extension local storage.
|
||||
const dir = tmp.dirSync();
|
||||
let storagePath = realpathSync(dir.name);
|
||||
if (storagePath.substring(0, 2).match(/[A-Z]:/)) {
|
||||
storagePath =
|
||||
storagePath.substring(0, 1).toLocaleLowerCase() +
|
||||
storagePath.substring(1);
|
||||
}
|
||||
setStoragePath(storagePath);
|
||||
|
||||
removeStorage = dir.removeCallback;
|
||||
|
||||
// Activate the extension
|
||||
await extensions.getExtension("GitHub.vscode-codeql")?.activate();
|
||||
}
|
||||
@@ -28,3 +46,14 @@ export async function beforeEachAction() {
|
||||
ConfigurationTarget.Workspace,
|
||||
);
|
||||
}
|
||||
|
||||
export async function afterAllAction() {
|
||||
// ensure temp directory is cleaned up
|
||||
try {
|
||||
removeStorage?.();
|
||||
} catch (e) {
|
||||
// we are exiting anyway so don't worry about it.
|
||||
// most likely the directory this is a test on Windows and some files are locked.
|
||||
console.warn(`Failed to remove storage directory '${storagePath}': ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user