Merge pull request #1559 from github/elenatanasoiu/download-variant-analysis-results
Download variant analysis results
This commit is contained in:
@@ -105,8 +105,12 @@ import { createInitialQueryInfo } from './run-queries-shared';
|
||||
import { LegacyQueryRunner } from './legacy-query-server/legacyRunner';
|
||||
import { QueryRunner } from './queryRunner';
|
||||
import { VariantAnalysisView } from './remote-queries/variant-analysis-view';
|
||||
import { VariantAnalysisMonitor } from './remote-queries/variant-analysis-monitor';
|
||||
import { VariantAnalysis } from './remote-queries/shared/variant-analysis';
|
||||
import {
|
||||
VariantAnalysis as VariantAnalysisApiResponse,
|
||||
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository
|
||||
} from './remote-queries/gh-api/variant-analysis';
|
||||
import { VariantAnalysisManager } from './remote-queries/variant-analysis-manager';
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -896,13 +900,23 @@ async function activateWithInstalledDistribution(
|
||||
})
|
||||
);
|
||||
|
||||
const variantAnalysisMonitor = new VariantAnalysisMonitor(ctx, logger);
|
||||
const variantAnalysisManager = new VariantAnalysisManager(ctx, logger);
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.monitorVariantAnalysis', async (
|
||||
variantAnalysis: VariantAnalysis,
|
||||
token: CancellationToken
|
||||
) => {
|
||||
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, token);
|
||||
await variantAnalysisManager.monitorVariantAnalysis(variantAnalysis, token);
|
||||
})
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.autoDownloadVariantAnalysisResult', async (
|
||||
scannedRepo: ApiVariantAnalysisScannedRepository,
|
||||
variantAnalysisSummary: VariantAnalysisApiResponse,
|
||||
token: CancellationToken
|
||||
) => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(scannedRepo, variantAnalysisSummary, token);
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -74,6 +74,19 @@ export async function getVariantAnalysisRepo(
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getVariantAnalysisRepoResult(
|
||||
credentials: Credentials,
|
||||
downloadUrl: string,
|
||||
): Promise<unknown> {
|
||||
const octokit = await credentials.getOctokit();
|
||||
|
||||
const response: OctokitResponse<VariantAnalysisRepoTask> = await octokit.request(
|
||||
`GET ${downloadUrl}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getRepositoryFromNwo(
|
||||
credentials: Credentials,
|
||||
owner: string,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import * as ghApiClient from './gh-api/gh-api-client';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import { CancellationToken, ExtensionContext } from 'vscode';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import { Logger } from '../logging';
|
||||
import { Credentials } from '../authentication';
|
||||
import { VariantAnalysisMonitor } from './variant-analysis-monitor';
|
||||
import {
|
||||
VariantAnalysis as VariantAnalysisApiResponse,
|
||||
VariantAnalysisRepoTask,
|
||||
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository
|
||||
} from './gh-api/variant-analysis';
|
||||
import { VariantAnalysis } from './shared/variant-analysis';
|
||||
import { getErrorMessage } from '../pure/helpers-pure';
|
||||
|
||||
export class VariantAnalysisManager extends DisposableObject {
|
||||
private readonly variantAnalysisMonitor: VariantAnalysisMonitor;
|
||||
|
||||
constructor(
|
||||
private readonly ctx: ExtensionContext,
|
||||
logger: Logger,
|
||||
) {
|
||||
super();
|
||||
this.variantAnalysisMonitor = new VariantAnalysisMonitor(ctx, logger);
|
||||
}
|
||||
|
||||
public async monitorVariantAnalysis(
|
||||
variantAnalysis: VariantAnalysis,
|
||||
cancellationToken: CancellationToken
|
||||
): Promise<void> {
|
||||
await this.variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
|
||||
}
|
||||
|
||||
public async autoDownloadVariantAnalysisResult(
|
||||
scannedRepo: ApiVariantAnalysisScannedRepository,
|
||||
variantAnalysisSummary: VariantAnalysisApiResponse,
|
||||
cancellationToken: CancellationToken
|
||||
): Promise<void> {
|
||||
|
||||
const credentials = await Credentials.initialize(this.ctx);
|
||||
if (!credentials) { throw Error('Error authenticating with GitHub'); }
|
||||
|
||||
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
let repoTask: VariantAnalysisRepoTask;
|
||||
try {
|
||||
repoTask = await ghApiClient.getVariantAnalysisRepo(
|
||||
credentials,
|
||||
variantAnalysisSummary.controller_repo.id,
|
||||
variantAnalysisSummary.id,
|
||||
scannedRepo.repository.id
|
||||
);
|
||||
}
|
||||
catch (e) { throw new Error(`Could not download the results for variant analysis with id: ${variantAnalysisSummary.id}. Error: ${getErrorMessage(e)}`); }
|
||||
|
||||
if (repoTask.artifact_url) {
|
||||
const resultDirectory = path.join(
|
||||
this.ctx.globalStorageUri.fsPath,
|
||||
'variant-analyses',
|
||||
`${variantAnalysisSummary.id}`,
|
||||
scannedRepo.repository.full_name
|
||||
);
|
||||
|
||||
const storagePath = path.join(
|
||||
resultDirectory,
|
||||
scannedRepo.repository.full_name
|
||||
);
|
||||
|
||||
const result = await ghApiClient.getVariantAnalysisRepoResult(
|
||||
credentials,
|
||||
repoTask.artifact_url
|
||||
);
|
||||
|
||||
fs.mkdirSync(resultDirectory, { recursive: true });
|
||||
await fs.writeFile(storagePath, JSON.stringify(result, null, 2), 'utf8');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtensionContext, CancellationToken, commands } from 'vscode';
|
||||
import { Credentials } from '../authentication';
|
||||
import { Logger } from '../logging';
|
||||
import * as ghApiClient from './gh-api/gh-api-client';
|
||||
@@ -17,14 +17,14 @@ export class VariantAnalysisMonitor {
|
||||
public static sleepTime = 5000;
|
||||
|
||||
constructor(
|
||||
private readonly extensionContext: vscode.ExtensionContext,
|
||||
private readonly extensionContext: ExtensionContext,
|
||||
private readonly logger: Logger
|
||||
) {
|
||||
}
|
||||
|
||||
public async monitorVariantAnalysis(
|
||||
variantAnalysis: VariantAnalysis,
|
||||
cancellationToken: vscode.CancellationToken
|
||||
cancellationToken: CancellationToken
|
||||
): Promise<VariantAnalysisMonitorResult> {
|
||||
|
||||
const credentials = await Credentials.initialize(this.extensionContext);
|
||||
@@ -64,6 +64,7 @@ export class VariantAnalysisMonitor {
|
||||
if (variantAnalysisSummary.scanned_repositories) {
|
||||
variantAnalysisSummary.scanned_repositories.forEach(scannedRepo => {
|
||||
if (!scannedReposDownloaded.includes(scannedRepo.repository.id) && scannedRepo.analysis_status === 'succeeded') {
|
||||
void commands.executeCommand('codeQL.autoDownloadVariantAnalysisResult', scannedRepo, variantAnalysisSummary);
|
||||
scannedReposDownloaded.push(scannedRepo.repository.id);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { assert, expect } from 'chai';
|
||||
import * as path from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
import { CancellationToken, extensions, QuickPickItem, Uri, window } from 'vscode';
|
||||
import { CancellationTokenSource, extensions, QuickPickItem, Uri, window } from 'vscode';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as yaml from 'js-yaml';
|
||||
@@ -32,7 +32,7 @@ describe('Remote queries', function() {
|
||||
|
||||
let cli: CodeQLCliServer;
|
||||
let credentials: Credentials = {} as unknown as Credentials;
|
||||
let token: CancellationToken;
|
||||
let cancellationTokenSource: CancellationTokenSource;
|
||||
let progress: sinon.SinonSpy;
|
||||
let showQuickPickSpy: sinon.SinonStub;
|
||||
let getRepositoryFromNwoStub: sinon.SinonStub;
|
||||
@@ -55,9 +55,15 @@ describe('Remote queries', function() {
|
||||
this.skip();
|
||||
}
|
||||
credentials = {} as unknown as Credentials;
|
||||
token = {
|
||||
isCancellationRequested: false
|
||||
} as unknown as CancellationToken;
|
||||
|
||||
cancellationTokenSource = {
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: sandbox.stub()
|
||||
},
|
||||
cancel: sandbox.stub(),
|
||||
dispose: sandbox.stub()
|
||||
};
|
||||
|
||||
progress = sandbox.spy();
|
||||
// Should not have asked for a language
|
||||
@@ -88,7 +94,7 @@ describe('Remote queries', function() {
|
||||
it('should run a remote query that is part of a qlpack', async () => {
|
||||
const fileUri = getFile('data-remote-qlpack/in-pack.ql');
|
||||
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
expect(querySubmissionResult).to.be.ok;
|
||||
const queryPackRootDir = querySubmissionResult!.queryDirPath!;
|
||||
printDirectoryContents(queryPackRootDir);
|
||||
@@ -149,7 +155,7 @@ describe('Remote queries', function() {
|
||||
it('should run a remote query that is not part of a qlpack', async () => {
|
||||
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
|
||||
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
expect(querySubmissionResult).to.be.ok;
|
||||
const queryPackRootDir = querySubmissionResult!.queryDirPath!;
|
||||
|
||||
@@ -212,7 +218,7 @@ describe('Remote queries', function() {
|
||||
it('should run a remote query that is nested inside a qlpack', async () => {
|
||||
const fileUri = getFile('data-remote-qlpack-nested/subfolder/in-pack.ql');
|
||||
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
expect(querySubmissionResult).to.be.ok;
|
||||
const queryPackRootDir = querySubmissionResult!.queryDirPath!;
|
||||
|
||||
@@ -274,9 +280,9 @@ describe('Remote queries', function() {
|
||||
it('should cancel a run before uploading', async () => {
|
||||
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
|
||||
|
||||
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
|
||||
token.isCancellationRequested = true;
|
||||
cancellationTokenSource.token.isCancellationRequested = true;
|
||||
|
||||
try {
|
||||
await promise;
|
||||
@@ -300,7 +306,7 @@ describe('Remote queries', function() {
|
||||
it('should run a variant analysis that is part of a qlpack', async () => {
|
||||
const fileUri = getFile('data-remote-qlpack/in-pack.ql');
|
||||
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
expect(querySubmissionResult).to.be.ok;
|
||||
const variantAnalysis = querySubmissionResult!.variantAnalysis!;
|
||||
expect(variantAnalysis.id).to.be.equal(mockApiResponse.id);
|
||||
@@ -313,7 +319,7 @@ describe('Remote queries', function() {
|
||||
it('should run a remote query that is not part of a qlpack', async () => {
|
||||
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
|
||||
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
expect(querySubmissionResult).to.be.ok;
|
||||
const variantAnalysis = querySubmissionResult!.variantAnalysis!;
|
||||
expect(variantAnalysis.id).to.be.equal(mockApiResponse.id);
|
||||
@@ -326,7 +332,7 @@ describe('Remote queries', function() {
|
||||
it('should run a remote query that is nested inside a qlpack', async () => {
|
||||
const fileUri = getFile('data-remote-qlpack-nested/subfolder/in-pack.ql');
|
||||
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
expect(querySubmissionResult).to.be.ok;
|
||||
const variantAnalysis = querySubmissionResult!.variantAnalysis!;
|
||||
expect(variantAnalysis.id).to.be.equal(mockApiResponse.id);
|
||||
@@ -339,9 +345,9 @@ describe('Remote queries', function() {
|
||||
it('should cancel a run before uploading', async () => {
|
||||
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
|
||||
|
||||
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, token);
|
||||
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
|
||||
|
||||
token.isCancellationRequested = true;
|
||||
cancellationTokenSource.token.isCancellationRequested = true;
|
||||
|
||||
try {
|
||||
await promise;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { CancellationTokenSource, extensions } from 'vscode';
|
||||
import { CodeQLExtensionInterface } from '../../../extension';
|
||||
import { logger } from '../../../logging';
|
||||
import * as config from '../../../config';
|
||||
import * as ghApiClient from '../../../remote-queries/gh-api/gh-api-client';
|
||||
import { Credentials } from '../../../authentication';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
import { VariantAnalysisManager } from '../../../remote-queries/variant-analysis-manager';
|
||||
import {
|
||||
VariantAnalysis as VariantAnalysisApiResponse,
|
||||
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository
|
||||
} from '../../../remote-queries/gh-api/variant-analysis';
|
||||
import { createMockApiResponse } from '../../factories/remote-queries/gh-api/variant-analysis-api-response';
|
||||
import { createMockScannedRepos } from '../../factories/remote-queries/gh-api/scanned-repositories';
|
||||
import { createMockVariantAnalysisRepoTask } from '../../factories/remote-queries/gh-api/variant-analysis-repo-task';
|
||||
|
||||
describe('Variant Analysis Manager', async function() {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let cancellationTokenSource: CancellationTokenSource;
|
||||
let variantAnalysisManager: VariantAnalysisManager;
|
||||
let variantAnalysis: VariantAnalysisApiResponse;
|
||||
let scannedRepos: ApiVariantAnalysisScannedRepository[];
|
||||
let getVariantAnalysisRepoStub: sinon.SinonStub;
|
||||
let getVariantAnalysisRepoResultStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.createSandbox();
|
||||
sandbox.stub(logger, 'log');
|
||||
sandbox.stub(config, 'isVariantAnalysisLiveResultsEnabled').returns(false);
|
||||
sandbox.stub(fs, 'mkdirSync');
|
||||
sandbox.stub(fs, 'writeFile');
|
||||
|
||||
cancellationTokenSource = {
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: sandbox.stub()
|
||||
},
|
||||
cancel: sandbox.stub(),
|
||||
dispose: sandbox.stub()
|
||||
};
|
||||
|
||||
scannedRepos = createMockScannedRepos();
|
||||
variantAnalysis = createMockApiResponse('in_progress', scannedRepos);
|
||||
|
||||
try {
|
||||
const extension = await extensions.getExtension<CodeQLExtensionInterface | Record<string, never>>('GitHub.vscode-codeql')!.activate();
|
||||
variantAnalysisManager = new VariantAnalysisManager(extension.ctx, logger);
|
||||
} catch (e) {
|
||||
fail(e as Error);
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('when credentials are invalid', async () => {
|
||||
beforeEach(async () => { sandbox.stub(Credentials, 'initialize').resolves(undefined); });
|
||||
|
||||
it('should return early if credentials are wrong', async () => {
|
||||
try {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
} catch (error: any) {
|
||||
expect(error.message).to.equal('Error authenticating with GitHub');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when credentials are valid', async () => {
|
||||
let getOctokitStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockCredentials = {
|
||||
getOctokit: () => Promise.resolve({
|
||||
request: getOctokitStub
|
||||
})
|
||||
} as unknown as Credentials;
|
||||
sandbox.stub(Credentials, 'initialize').resolves(mockCredentials);
|
||||
});
|
||||
|
||||
describe('when the artifact_url is missing', async () => {
|
||||
beforeEach(async () => {
|
||||
const dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||
delete dummyRepoTask.artifact_url;
|
||||
getVariantAnalysisRepoStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepo').resolves(dummyRepoTask);
|
||||
|
||||
const dummyResult = 'this-is-a-repo-result';
|
||||
getVariantAnalysisRepoResultStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepoResult').resolves(dummyResult);
|
||||
});
|
||||
|
||||
it('should not try to download the result', async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub.notCalled).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the artifact_url is present', async () => {
|
||||
beforeEach(async () => {
|
||||
const dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||
getVariantAnalysisRepoStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepo').resolves(dummyRepoTask);
|
||||
|
||||
const dummyResult = 'this-is-a-repo-result';
|
||||
getVariantAnalysisRepoResultStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepoResult').resolves(dummyResult);
|
||||
});
|
||||
|
||||
it('should return early if variant analysis is cancelled', async () => {
|
||||
cancellationTokenSource.token.isCancellationRequested = true;
|
||||
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub.notCalled).to.be.true;
|
||||
});
|
||||
|
||||
it('should fetch a repo task', async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoStub.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should fetch a repo result', async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should save the result to disk', async () => {
|
||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||
scannedRepos[0],
|
||||
variantAnalysis,
|
||||
cancellationTokenSource.token
|
||||
);
|
||||
|
||||
expect(getVariantAnalysisRepoResultStub.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { CancellationToken, extensions } from 'vscode';
|
||||
import { CancellationTokenSource, extensions } from 'vscode';
|
||||
import { CodeQLExtensionInterface } from '../../../extension';
|
||||
import { logger } from '../../../logging';
|
||||
import * as config from '../../../config';
|
||||
@@ -13,32 +13,35 @@ import {
|
||||
VariantAnalysisFailureReason
|
||||
} from '../../../remote-queries/gh-api/variant-analysis';
|
||||
import { createFailedMockApiResponse, createMockApiResponse } from '../../factories/remote-queries/gh-api/variant-analysis-api-response';
|
||||
import { VariantAnalysisStatus } from '../../../remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysis, VariantAnalysisStatus } from '../../../remote-queries/shared/variant-analysis';
|
||||
import { createMockScannedRepos } from '../../factories/remote-queries/gh-api/scanned-repositories';
|
||||
import { processFailureReason } from '../../../remote-queries/variant-analysis-processor';
|
||||
import { Credentials } from '../../../authentication';
|
||||
import { createMockVariantAnalysis } from '../../factories/remote-queries/shared/variant-analysis';
|
||||
|
||||
describe('Variant Analysis Monitor', async function() {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let mockGetVariantAnalysis: sinon.SinonStub;
|
||||
let cancellationToken: CancellationToken;
|
||||
let cancellationTokenSource: CancellationTokenSource;
|
||||
let variantAnalysisMonitor: VariantAnalysisMonitor;
|
||||
let variantAnalysis: any;
|
||||
let variantAnalysis: VariantAnalysis;
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.createSandbox();
|
||||
sandbox.stub(logger, 'log');
|
||||
sandbox.stub(config, 'isVariantAnalysisLiveResultsEnabled').returns(false);
|
||||
|
||||
cancellationToken = {
|
||||
isCancellationRequested: false
|
||||
} as unknown as CancellationToken;
|
||||
|
||||
variantAnalysis = {
|
||||
id: 123,
|
||||
controllerRepoId: 1,
|
||||
cancellationTokenSource = {
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: sandbox.stub()
|
||||
},
|
||||
cancel: sandbox.stub(),
|
||||
dispose: sandbox.stub()
|
||||
};
|
||||
|
||||
variantAnalysis = createMockVariantAnalysis();
|
||||
|
||||
try {
|
||||
const extension = await extensions.getExtension<CodeQLExtensionInterface | Record<string, never>>('GitHub.vscode-codeql')!.activate();
|
||||
variantAnalysisMonitor = new VariantAnalysisMonitor(extension.ctx, logger);
|
||||
@@ -58,7 +61,7 @@ describe('Variant Analysis Monitor', async function() {
|
||||
|
||||
it('should return early if credentials are wrong', async () => {
|
||||
try {
|
||||
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
|
||||
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationTokenSource.token);
|
||||
} catch (error: any) {
|
||||
expect(error.message).to.equal('Error authenticating with GitHub');
|
||||
}
|
||||
@@ -76,9 +79,9 @@ describe('Variant Analysis Monitor', async function() {
|
||||
});
|
||||
|
||||
it('should return early if variant analysis is cancelled', async () => {
|
||||
cancellationToken.isCancellationRequested = true;
|
||||
cancellationTokenSource.token.isCancellationRequested = true;
|
||||
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationTokenSource.token);
|
||||
|
||||
expect(result).to.eql({ status: 'Cancelled', error: 'Variant Analysis was canceled.' });
|
||||
});
|
||||
@@ -92,14 +95,13 @@ describe('Variant Analysis Monitor', async function() {
|
||||
});
|
||||
|
||||
it('should mark as failed locally and stop monitoring', async () => {
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
|
||||
variantAnalysis = result.variantAnalysis;
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationTokenSource.token);
|
||||
|
||||
expect(mockGetVariantAnalysis.calledOnce).to.be.true;
|
||||
expect(result.status).to.eql('Failed');
|
||||
expect(result.error).to.eql(`Variant Analysis has failed: ${mockFailedApiResponse.failure_reason}`);
|
||||
expect(variantAnalysis.status).to.equal(VariantAnalysisStatus.Failed);
|
||||
expect(variantAnalysis.failureReason).to.equal(processFailureReason(mockFailedApiResponse.failure_reason as VariantAnalysisFailureReason));
|
||||
expect(result.variantAnalysis?.status).to.equal(VariantAnalysisStatus.Failed);
|
||||
expect(result.variantAnalysis?.failureReason).to.equal(processFailureReason(mockFailedApiResponse.failure_reason as VariantAnalysisFailureReason));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +117,7 @@ describe('Variant Analysis Monitor', async function() {
|
||||
});
|
||||
|
||||
it('should succeed and return a list of scanned repo ids', async () => {
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationTokenSource.token);
|
||||
const scannedRepoIds = scannedRepos.filter(r => r.analysis_status == 'succeeded').map(r => r.repository.id);
|
||||
|
||||
expect(result.status).to.equal('CompletedSuccessfully');
|
||||
@@ -133,7 +135,7 @@ describe('Variant Analysis Monitor', async function() {
|
||||
});
|
||||
|
||||
it('should succeed and return an empty list of scanned repo ids', async () => {
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationTokenSource.token);
|
||||
|
||||
expect(result.status).to.equal('CompletedSuccessfully');
|
||||
expect(result.scannedReposDownloaded).to.eql([]);
|
||||
@@ -148,7 +150,7 @@ describe('Variant Analysis Monitor', async function() {
|
||||
});
|
||||
|
||||
it('should succeed and return an empty list of scanned repo ids', async () => {
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
|
||||
const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationTokenSource.token);
|
||||
|
||||
expect(result.status).to.equal('CompletedSuccessfully');
|
||||
expect(result.scannedReposDownloaded).to.eql([]);
|
||||
|
||||
@@ -25,8 +25,8 @@ describe('Variant Analysis processor', function() {
|
||||
const { access_mismatch_repos, no_codeql_db_repos, not_found_repo_nwos, over_limit_repos } = skippedRepos;
|
||||
|
||||
expect(result).to.eql({
|
||||
'id': 123,
|
||||
'controllerRepoId': 456,
|
||||
'id': mockApiResponse.id,
|
||||
'controllerRepoId': mockApiResponse.controller_repo.id,
|
||||
'query': {
|
||||
'filePath': 'query-file-path',
|
||||
'language': VariantAnalysisQueryLanguage.Javascript,
|
||||
@@ -36,7 +36,7 @@ describe('Variant Analysis processor', function() {
|
||||
'repositories': ['1', '2', '3'],
|
||||
},
|
||||
'status': 'succeeded',
|
||||
'actionsWorkflowRunId': 456,
|
||||
'actionsWorkflowRunId': mockApiResponse.actions_workflow_run_id,
|
||||
'scannedRepos': [
|
||||
transformScannedRepo(VariantAnalysisRepoStatus.Succeeded, scannedRepos[0]),
|
||||
transformScannedRepo(VariantAnalysisRepoStatus.Pending, scannedRepos[1]),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import {
|
||||
VariantAnalysis as VariantAnalysisApiResponse,
|
||||
VariantAnalysisScannedRepository,
|
||||
@@ -15,19 +16,20 @@ export function createMockApiResponse(
|
||||
scannedRepos: VariantAnalysisScannedRepository[] = createMockScannedRepos(),
|
||||
skippedRepos: VariantAnalysisSkippedRepositories = createMockSkippedRepos()
|
||||
): VariantAnalysisApiResponse {
|
||||
|
||||
const variantAnalysis: VariantAnalysisApiResponse = {
|
||||
id: 123,
|
||||
id: faker.datatype.number(),
|
||||
controller_repo: {
|
||||
id: 456,
|
||||
id: faker.datatype.number(),
|
||||
name: 'pickles',
|
||||
full_name: 'github/pickles',
|
||||
private: false,
|
||||
},
|
||||
actor_id: 123,
|
||||
actor_id: faker.datatype.number(),
|
||||
query_language: VariantAnalysisQueryLanguage.Javascript,
|
||||
query_pack_url: 'https://example.com/foo',
|
||||
status: status,
|
||||
actions_workflow_run_id: 456,
|
||||
actions_workflow_run_id: faker.datatype.number(),
|
||||
scanned_repositories: scannedRepos,
|
||||
skipped_repositories: skippedRepos
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { VariantAnalysisRepoTask } from '../../../../remote-queries/gh-api/variant-analysis';
|
||||
import { VariantAnalysisRepoStatus } from '../../../../remote-queries/shared/variant-analysis';
|
||||
|
||||
export function createMockVariantAnalysisRepoTask(): VariantAnalysisRepoTask {
|
||||
return {
|
||||
repository: {
|
||||
id: faker.datatype.number(),
|
||||
name: faker.random.word(),
|
||||
full_name: 'github/' + faker.random.word(),
|
||||
private: false,
|
||||
},
|
||||
analysis_status: VariantAnalysisRepoStatus.Succeeded,
|
||||
result_count: faker.datatype.number(),
|
||||
artifact_size_in_bytes: faker.datatype.number(),
|
||||
artifact_url: 'https://www.pickles.com'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import {
|
||||
VariantAnalysisRepoStatus,
|
||||
VariantAnalysisScannedRepository
|
||||
} from '../../../../remote-queries/shared/variant-analysis';
|
||||
|
||||
export function createMockScannedRepo(
|
||||
name: string,
|
||||
isPrivate: boolean,
|
||||
analysisStatus: VariantAnalysisRepoStatus,
|
||||
): VariantAnalysisScannedRepository {
|
||||
return {
|
||||
repository: {
|
||||
id: faker.datatype.number(),
|
||||
fullName: 'github/' + name,
|
||||
private: isPrivate,
|
||||
},
|
||||
analysisStatus: analysisStatus,
|
||||
resultCount: faker.datatype.number(),
|
||||
artifactSizeInBytes: faker.datatype.number()
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockScannedRepos(
|
||||
statuses: VariantAnalysisRepoStatus[] = [
|
||||
VariantAnalysisRepoStatus.Succeeded,
|
||||
VariantAnalysisRepoStatus.Pending,
|
||||
VariantAnalysisRepoStatus.InProgress,
|
||||
]
|
||||
): VariantAnalysisScannedRepository[] {
|
||||
return statuses.map(status => createMockScannedRepo(`mona-${status}`, false, status));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import {
|
||||
VariantAnalysisSkippedRepositories,
|
||||
VariantAnalysisSkippedRepositoryGroup
|
||||
} from '../../../../remote-queries/shared/variant-analysis';
|
||||
|
||||
export function createMockSkippedRepos(): VariantAnalysisSkippedRepositories {
|
||||
return {
|
||||
accessMismatchRepos: createMockSkippedRepoGroup(),
|
||||
noCodeqlDbRepos: createMockSkippedRepoGroup(),
|
||||
notFoundRepos: createMockNotFoundRepoGroup(),
|
||||
overLimitRepos: createMockSkippedRepoGroup()
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockSkippedRepoGroup(): VariantAnalysisSkippedRepositoryGroup {
|
||||
return {
|
||||
repositoryCount: 2,
|
||||
repositories: [
|
||||
{
|
||||
id: faker.datatype.number(),
|
||||
fullName: 'github/' + faker.random.word(),
|
||||
},
|
||||
{
|
||||
id: faker.datatype.number(),
|
||||
fullName: 'github/' + faker.random.word(),
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockNotFoundRepoGroup(): VariantAnalysisSkippedRepositoryGroup {
|
||||
return {
|
||||
repositoryCount: 2,
|
||||
repositories: [
|
||||
{
|
||||
fullName: 'github/' + faker.random.word(),
|
||||
},
|
||||
{
|
||||
fullName: 'github/' + faker.random.word(),
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { VariantAnalysisQueryLanguage, VariantAnalysisSubmission } from '../../../../remote-queries/shared/variant-analysis';
|
||||
|
||||
export function createMockSubmission(): VariantAnalysisSubmission {
|
||||
return {
|
||||
startTime: 1234,
|
||||
controllerRepoId: 5678,
|
||||
startTime: faker.datatype.number(),
|
||||
controllerRepoId: faker.datatype.number(),
|
||||
actionRepoRef: 'repo-ref',
|
||||
query: {
|
||||
name: 'query-name',
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisQueryLanguage,
|
||||
VariantAnalysisScannedRepository,
|
||||
VariantAnalysisSkippedRepositories,
|
||||
VariantAnalysisStatus,
|
||||
} from '../../../../remote-queries/shared/variant-analysis';
|
||||
import { createMockScannedRepos } from './scanned-repositories';
|
||||
import { createMockSkippedRepos } from './skipped-repositories';
|
||||
|
||||
export function createMockVariantAnalysis(
|
||||
status: VariantAnalysisStatus = VariantAnalysisStatus.InProgress,
|
||||
scannedRepos: VariantAnalysisScannedRepository[] = createMockScannedRepos(),
|
||||
skippedRepos: VariantAnalysisSkippedRepositories = createMockSkippedRepos()
|
||||
): VariantAnalysis {
|
||||
const variantAnalysis: VariantAnalysis = {
|
||||
id: faker.datatype.number(),
|
||||
controllerRepoId: faker.datatype.number(),
|
||||
query: {
|
||||
name: 'a-query-name',
|
||||
filePath: 'a-query-file-path',
|
||||
language: VariantAnalysisQueryLanguage.Javascript
|
||||
},
|
||||
databases: {
|
||||
repositories: ['1', '2', '3'],
|
||||
},
|
||||
status: status,
|
||||
actionsWorkflowRunId: faker.datatype.number(),
|
||||
scannedRepos: scannedRepos,
|
||||
skippedRepos: skippedRepos
|
||||
};
|
||||
|
||||
return variantAnalysis;
|
||||
}
|
||||
Reference in New Issue
Block a user