diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 855ca9974..33636ba62 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -467,16 +467,16 @@ async function activateWithInstalledDistribution( const localQueryResultsView = new ResultsView(ctx, dbm, cliServer, queryServerLogger, labelProvider); ctx.subscriptions.push(localQueryResultsView); - void logger.log('Initializing remote queries manager.'); - const rqm = new RemoteQueriesManager(ctx, cliServer, queryStorageDir, logger); - ctx.subscriptions.push(rqm); - void logger.log('Initializing variant analysis manager.'); const variantAnalysisStorageDir = path.join(ctx.globalStorageUri.fsPath, 'variant-analyses'); await fs.ensureDir(variantAnalysisStorageDir); const variantAnalysisManager = new VariantAnalysisManager(ctx, cliServer, variantAnalysisStorageDir, logger); ctx.subscriptions.push(variantAnalysisManager); + void logger.log('Initializing remote queries manager.'); + const rqm = new RemoteQueriesManager(ctx, cliServer, queryStorageDir, logger, variantAnalysisManager); + ctx.subscriptions.push(rqm); + void logger.log('Initializing query history.'); const qhm = new QueryHistoryManager( qs, diff --git a/extensions/ql-vscode/src/remote-queries/gh-api/gh-api-client.ts b/extensions/ql-vscode/src/remote-queries/gh-api/gh-api-client.ts index c9bcbd7fa..1e9d6c290 100644 --- a/extensions/ql-vscode/src/remote-queries/gh-api/gh-api-client.ts +++ b/extensions/ql-vscode/src/remote-queries/gh-api/gh-api-client.ts @@ -77,12 +77,9 @@ export async function getVariantAnalysisRepo( export async function getVariantAnalysisRepoResult( credentials: Credentials, downloadUrl: string, -): Promise { +): Promise { const octokit = await credentials.getOctokit(); - - const response: OctokitResponse = await octokit.request( - `GET ${downloadUrl}` - ); + const response = await octokit.request(`GET ${downloadUrl}`); return response.data; } diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts index 71319ecba..6350c0b5f 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts @@ -22,6 +22,7 @@ import { assertNever } from '../pure/helpers-pure'; import { QueryStatus } from '../query-status'; import { DisposableObject } from '../pure/disposable-object'; import { AnalysisResults } from './shared/analysis-result'; +import { VariantAnalysisManager } from './variant-analysis-manager'; const autoDownloadMaxSize = 300 * 1024; const autoDownloadMaxCount = 100; @@ -56,6 +57,7 @@ export class RemoteQueriesManager extends DisposableObject { private readonly remoteQueriesMonitor: RemoteQueriesMonitor; private readonly analysesResultsManager: AnalysesResultsManager; + private readonly variantAnalysisManager: VariantAnalysisManager; private readonly view: RemoteQueriesView; constructor( @@ -63,11 +65,13 @@ export class RemoteQueriesManager extends DisposableObject { private readonly cliServer: CodeQLCliServer, private readonly storagePath: string, logger: Logger, + variantAnalysisManager: VariantAnalysisManager, ) { super(); this.analysesResultsManager = new AnalysesResultsManager(ctx, cliServer, storagePath, logger); this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager); this.remoteQueriesMonitor = new RemoteQueriesMonitor(ctx, logger); + this.variantAnalysisManager = variantAnalysisManager; this.remoteQueryAddedEventEmitter = this.push(new EventEmitter()); this.remoteQueryRemovedEventEmitter = this.push(new EventEmitter()); @@ -123,7 +127,8 @@ export class RemoteQueriesManager extends DisposableObject { credentials, uri || window.activeTextEditor?.document.uri, false, progress, - token); + token, + this.variantAnalysisManager); if (querySubmission?.query) { const query = querySubmission.query; diff --git a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts index d6505ac67..bc1a4e41b 100644 --- a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts +++ b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts @@ -29,6 +29,7 @@ import { getRepositorySelection, isValidSelection, RepositorySelection } from '. import { parseVariantAnalysisQueryLanguage, VariantAnalysisSubmission } from './shared/variant-analysis'; import { Repository } from './shared/repository'; import { processVariantAnalysis } from './variant-analysis-processor'; +import { VariantAnalysisManager } from './variant-analysis-manager'; export interface QlPack { name: string; @@ -182,7 +183,8 @@ export async function runRemoteQuery( uri: Uri | undefined, dryRun: boolean, progress: ProgressCallback, - token: CancellationToken + token: CancellationToken, + variantAnalysisManager: VariantAnalysisManager ): Promise { if (!(await cliServer.cliConstraints.supportsRemoteQueries())) { throw new Error(`Variant analysis is not supported by this version of CodeQL. Please upgrade to v${cli.CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES @@ -273,6 +275,8 @@ export async function runRemoteQuery( const processedVariantAnalysis = processVariantAnalysis(variantAnalysisSubmission, variantAnalysisResponse); + variantAnalysisManager.onVariantAnalysisSubmitted(processedVariantAnalysis); + void logger.log(`Variant analysis:\n${JSON.stringify(processedVariantAnalysis, null, 2)}`); void showAndLogInformationMessage(`Variant analysis ${processedVariantAnalysis.query.name} submitted for processing`); diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts index f8a723bcc..f6620756b 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts @@ -1,5 +1,5 @@ import * as ghApiClient from './gh-api/gh-api-client'; -import { CancellationToken, ExtensionContext } from 'vscode'; +import { CancellationToken, EventEmitter, ExtensionContext } from 'vscode'; import { DisposableObject } from '../pure/disposable-object'; import { Logger } from '../logging'; import { Credentials } from '../authentication'; @@ -22,6 +22,9 @@ import { VariantAnalysisResultsManager } from './variant-analysis-results-manage import { CodeQLCliServer } from '../cli'; export class VariantAnalysisManager extends DisposableObject implements VariantAnalysisViewManager { + private readonly _onVariantAnalysisAdded = this.push(new EventEmitter()); + readonly onVariantAnalysisAdded = this._onVariantAnalysisAdded.event; + private readonly variantAnalysisMonitor: VariantAnalysisMonitor; private readonly variantAnalysisResultsManager: VariantAnalysisResultsManager; private readonly variantAnalyses = new Map(); @@ -91,6 +94,10 @@ export class VariantAnalysisManager extends DisposableObject implements VariantA await this.getView(variantAnalysis.id)?.updateView(variantAnalysis); } + public onVariantAnalysisSubmitted(variantAnalysis: VariantAnalysis): void { + this._onVariantAnalysisAdded.fire(variantAnalysis); + } + private async onRepoResultLoaded(repositoryResult: VariantAnalysisScannedRepositoryResult): Promise { await this.getView(repositoryResult.variantAnalysisId)?.sendRepositoryResults([repositoryResult]); } diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-results-manager.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-results-manager.ts index d2bf72128..e9ec3e6ea 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-results-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-results-manager.ts @@ -14,6 +14,7 @@ import { DisposableObject, DisposeHandler } from '../pure/disposable-object'; import { VariantAnalysisRepoTask } from './gh-api/variant-analysis'; import * as ghApiClient from './gh-api/gh-api-client'; import { EventEmitter } from 'vscode'; +import { unzipFile } from '../pure/zip'; type CacheKey = `${number}/${string}`; @@ -26,6 +27,7 @@ export type ResultDownloadedEvent = { export class VariantAnalysisResultsManager extends DisposableObject { private static readonly REPO_TASK_FILENAME = 'repo_task.json'; + private static readonly RESULTS_DIRECTORY = 'results'; private readonly cachedResults: Map; @@ -60,9 +62,17 @@ export class VariantAnalysisResultsManager extends DisposableObject { repoTask.artifact_url ); - fs.mkdirSync(resultDirectory, { recursive: true }); + if (!(await fs.pathExists(resultDirectory))) { + await fs.mkdir(resultDirectory, { recursive: true }); + } + await fs.outputJson(path.join(resultDirectory, VariantAnalysisResultsManager.REPO_TASK_FILENAME), repoTask); - await fs.writeFile(path.join(resultDirectory, 'results.zip'), JSON.stringify(result, null, 2), 'utf8'); + + const zipFilePath = path.join(resultDirectory, 'results.zip'); + const unzippedFilesDirectory = path.join(resultDirectory, VariantAnalysisResultsManager.RESULTS_DIRECTORY); + + fs.writeFileSync(zipFilePath, Buffer.from(result)); + await unzipFile(zipFilePath, unzippedFilesDirectory); this._onResultDownloaded.fire({ variantAnalysisId, @@ -107,8 +117,9 @@ export class VariantAnalysisResultsManager extends DisposableObject { const fileLinkPrefix = this.createGitHubDotcomFileLinkPrefix(repoTask.repository.full_name, repoTask.database_commit_sha); - const sarifPath = path.join(storageDirectory, 'results.sarif'); - const bqrsPath = path.join(storageDirectory, 'results.bqrs'); + const resultsDirectory = path.join(storageDirectory, VariantAnalysisResultsManager.RESULTS_DIRECTORY); + const sarifPath = path.join(resultsDirectory, 'results.sarif'); + const bqrsPath = path.join(resultsDirectory, 'results.bqrs'); if (await fs.pathExists(sarifPath)) { const interpretedResults = await this.readSarifResults(sarifPath, fileLinkPrefix); @@ -161,7 +172,7 @@ export class VariantAnalysisResultsManager extends DisposableObject { ); } - private getRepoStorageDirectory(variantAnalysisId: number, fullName: string): string { + public getRepoStorageDirectory(variantAnalysisId: number, fullName: string): string { return path.join( this.getStorageDirectory(variantAnalysisId), fullName diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data/variant-analysis-results.zip b/extensions/ql-vscode/src/vscode-tests/cli-integration/data/variant-analysis-results.zip new file mode 100644 index 000000000..ea610970b Binary files /dev/null and b/extensions/ql-vscode/src/vscode-tests/cli-integration/data/variant-analysis-results.zip differ diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts index 187169e6a..2991bfa8a 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts @@ -1,7 +1,7 @@ import { assert, expect } from 'chai'; import * as path from 'path'; import * as sinon from 'sinon'; -import { CancellationTokenSource, extensions, QuickPickItem, Uri, window } from 'vscode'; +import { CancellationTokenSource, ExtensionContext, extensions, QuickPickItem, Uri, window } from 'vscode'; import * as fs from 'fs-extra'; import * as os from 'os'; import * as yaml from 'js-yaml'; @@ -21,6 +21,9 @@ import { import { Repository } from '../../../remote-queries/gh-api/repository'; import { VariantAnalysisStatus } from '../../../remote-queries/shared/variant-analysis'; import { createMockApiResponse } from '../../factories/remote-queries/gh-api/variant-analysis-api-response'; +import { createMockExtensionContext } from '../../no-workspace'; +import { VariantAnalysisManager } from '../../../remote-queries/variant-analysis-manager'; +import { OutputChannelLogger } from '../../../logging'; describe('Remote queries', function() { const baseDir = path.join(__dirname, '../../../../src/vscode-tests/cli-integration'); @@ -37,6 +40,9 @@ describe('Remote queries', function() { let showQuickPickSpy: sinon.SinonStub; let getRepositoryFromNwoStub: sinon.SinonStub; let liveResultsStub: sinon.SinonStub; + let ctx: ExtensionContext; + let logger: any; + let variantAnalysisManager: VariantAnalysisManager; // use `function` so we have access to `this` beforeEach(async function() { @@ -49,6 +55,10 @@ describe('Remote queries', function() { throw new Error('Extension not initialized. Make sure cli is downloaded and installed properly.'); } + ctx = createMockExtensionContext(); + logger = new OutputChannelLogger('test-logger'); + variantAnalysisManager = new VariantAnalysisManager(ctx, cli, 'fake-storage-dir', logger); + if (!(await cli.cliConstraints.supportsRemoteQueries())) { console.log(`Remote queries are not supported on CodeQL CLI v${CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES }. Skipping this test.`); @@ -56,14 +66,7 @@ describe('Remote queries', function() { } credentials = {} as unknown as Credentials; - cancellationTokenSource = { - token: { - isCancellationRequested: false, - onCancellationRequested: sandbox.stub() - }, - cancel: sandbox.stub(), - dispose: sandbox.stub() - }; + cancellationTokenSource = new CancellationTokenSource(); progress = sandbox.spy(); // Should not have asked for a language @@ -94,7 +97,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, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const queryPackRootDir = querySubmissionResult!.queryDirPath!; printDirectoryContents(queryPackRootDir); @@ -155,7 +158,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, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const queryPackRootDir = querySubmissionResult!.queryDirPath!; @@ -218,7 +221,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, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const queryPackRootDir = querySubmissionResult!.queryDirPath!; @@ -280,9 +283,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, cancellationTokenSource.token); + const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); - cancellationTokenSource.token.isCancellationRequested = true; + cancellationTokenSource.cancel(); try { await promise; @@ -306,7 +309,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, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const variantAnalysis = querySubmissionResult!.variantAnalysis!; expect(variantAnalysis.id).to.be.equal(mockApiResponse.id); @@ -319,7 +322,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, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const variantAnalysis = querySubmissionResult!.variantAnalysis!; expect(variantAnalysis.id).to.be.equal(mockApiResponse.id); @@ -332,7 +335,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, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const variantAnalysis = querySubmissionResult!.variantAnalysis!; expect(variantAnalysis.id).to.be.equal(mockApiResponse.id); @@ -345,9 +348,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, cancellationTokenSource.token); + const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); - cancellationTokenSource.token.isCancellationRequested = true; + cancellationTokenSource.cancel(); try { await promise; diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts index af4a7f157..11b91eb15 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts @@ -7,10 +7,12 @@ 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 * as path from 'path'; import { VariantAnalysisManager } from '../../../remote-queries/variant-analysis-manager'; import { VariantAnalysis as VariantAnalysisApiResponse, + VariantAnalysisRepoTask, VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository } from '../../../remote-queries/gh-api/variant-analysis'; import { createMockApiResponse } from '../../factories/remote-queries/gh-api/variant-analysis-api-response'; @@ -36,14 +38,7 @@ describe('Variant Analysis Manager', async function() { sandbox.stub(fs, 'mkdirSync'); sandbox.stub(fs, 'writeFile'); - cancellationTokenSource = { - token: { - isCancellationRequested: false, - onCancellationRequested: sandbox.stub() - }, - cancel: sandbox.stub(), - dispose: sandbox.stub() - }; + cancellationTokenSource = new CancellationTokenSource(); scannedRepos = createMockScannedRepos(); variantAnalysis = createMockApiResponse('in_progress', scannedRepos); @@ -79,6 +74,7 @@ describe('Variant Analysis Manager', async function() { describe('when credentials are valid', async () => { let getOctokitStub: sinon.SinonStub; + let arrayBuffer: ArrayBuffer; beforeEach(async () => { const mockCredentials = { @@ -87,16 +83,18 @@ describe('Variant Analysis Manager', async function() { }) } as unknown as Credentials; sandbox.stub(Credentials, 'initialize').resolves(mockCredentials); + + const sourceFilePath = path.join(__dirname, '../../../../src/vscode-tests/cli-integration/data/variant-analysis-results.zip'); + arrayBuffer = fs.readFileSync(sourceFilePath).buffer; }); 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); + getVariantAnalysisRepoStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepo').resolves(dummyRepoTask); + getVariantAnalysisRepoResultStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepoResult').resolves(arrayBuffer); }); it('should not try to download the result', async () => { @@ -111,16 +109,17 @@ describe('Variant Analysis Manager', async function() { }); describe('when the artifact_url is present', async () => { - beforeEach(async () => { - const dummyRepoTask = createMockVariantAnalysisRepoTask(); - getVariantAnalysisRepoStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepo').resolves(dummyRepoTask); + let dummyRepoTask: VariantAnalysisRepoTask; - const dummyResult = 'this-is-a-repo-result'; - getVariantAnalysisRepoResultStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepoResult').resolves(dummyResult); + beforeEach(async () => { + dummyRepoTask = createMockVariantAnalysisRepoTask(); + + getVariantAnalysisRepoStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepo').resolves(dummyRepoTask); + getVariantAnalysisRepoResultStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepoResult').resolves(arrayBuffer); }); it('should return early if variant analysis is cancelled', async () => { - cancellationTokenSource.token.isCancellationRequested = true; + cancellationTokenSource.cancel(); await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], @@ -150,16 +149,6 @@ describe('Variant Analysis Manager', async function() { 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; - }); }); }); }); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts index 7ce204690..d38911875 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts @@ -31,14 +31,7 @@ describe('Variant Analysis Monitor', async function() { sandbox.stub(logger, 'log'); sandbox.stub(config, 'isVariantAnalysisLiveResultsEnabled').returns(false); - cancellationTokenSource = { - token: { - isCancellationRequested: false, - onCancellationRequested: sandbox.stub() - }, - cancel: sandbox.stub(), - dispose: sandbox.stub() - }; + cancellationTokenSource = new CancellationTokenSource(); variantAnalysis = createMockVariantAnalysis(); @@ -79,7 +72,7 @@ describe('Variant Analysis Monitor', async function() { }); it('should return early if variant analysis is cancelled', async () => { - cancellationTokenSource.token.isCancellationRequested = true; + cancellationTokenSource.cancel(); const result = await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationTokenSource.token); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts index f61186de4..b7d584b0a 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts @@ -5,6 +5,7 @@ import { CodeQLExtensionInterface } from '../../../extension'; import { logger } from '../../../logging'; import { Credentials } from '../../../authentication'; import * as fs from 'fs-extra'; +import * as path from 'path'; import { VariantAnalysisResultsManager } from '../../../remote-queries/variant-analysis-results-manager'; import { createMockVariantAnalysisRepoTask } from '../../factories/remote-queries/gh-api/variant-analysis-repo-task'; @@ -12,6 +13,7 @@ import { CodeQLCliServer } from '../../../cli'; import { storagePath } from '../global.helper'; import { faker } from '@faker-js/faker'; import * as ghApiClient from '../../../remote-queries/gh-api/gh-api-client'; +import { VariantAnalysisRepoTask } from '../../../remote-queries/gh-api/variant-analysis'; describe(VariantAnalysisResultsManager.name, () => { let sandbox: sinon.SinonSandbox; @@ -69,12 +71,29 @@ describe(VariantAnalysisResultsManager.name, () => { }); describe('when the artifact_url is present', async () => { - it('should save the result to disk', async () => { - const dummyRepoTask = createMockVariantAnalysisRepoTask(); + let dummyRepoTask: VariantAnalysisRepoTask; + let storageDirectory: string; + let arrayBuffer: ArrayBuffer; - const dummyResult = 'this-is-a-repo-result'; - getVariantAnalysisRepoResultStub = sandbox.stub(ghApiClient, 'getVariantAnalysisRepoResult').withArgs(mockCredentials, dummyRepoTask.artifact_url as string).resolves(dummyResult); + beforeEach(async () => { + dummyRepoTask = createMockVariantAnalysisRepoTask(); + storageDirectory = variantAnalysisResultsManager.getRepoStorageDirectory(variantAnalysisId, dummyRepoTask.repository.full_name); + const sourceFilePath = path.join(__dirname, '../../../../src/vscode-tests/cli-integration/data/variant-analysis-results.zip'); + arrayBuffer = fs.readFileSync(sourceFilePath).buffer; + + getVariantAnalysisRepoResultStub = sandbox + .stub(ghApiClient, 'getVariantAnalysisRepoResult') + .withArgs(mockCredentials, dummyRepoTask.artifact_url as string) + .resolves(arrayBuffer); + }); + + afterEach(async () => { + fs.removeSync(`${storageDirectory}/results.zip`); + fs.removeSync(`${storageDirectory}/results`); + }); + + it('should call the API to download the results', async () => { await variantAnalysisResultsManager.download( mockCredentials, variantAnalysisId, @@ -83,6 +102,26 @@ describe(VariantAnalysisResultsManager.name, () => { expect(getVariantAnalysisRepoResultStub.calledOnce).to.be.true; }); + + it('should save the results zip file to disk', async () => { + await variantAnalysisResultsManager.download( + mockCredentials, + variantAnalysisId, + dummyRepoTask + ); + + expect(fs.existsSync(`${storageDirectory}/results.zip`)).to.be.true; + }); + + it('should unzip the results in a `results/` folder', async () => { + await variantAnalysisResultsManager.download( + mockCredentials, + variantAnalysisId, + dummyRepoTask + ); + + expect(fs.existsSync(`${storageDirectory}/results/results.sarif`)).to.be.true; + }); }); }); });