diff --git a/extensions/ql-vscode/src/authentication.ts b/extensions/ql-vscode/src/authentication.ts index e46a3b80c..8fc055458 100644 --- a/extensions/ql-vscode/src/authentication.ts +++ b/extensions/ql-vscode/src/authentication.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import * as Octokit from "@octokit/rest"; import { retry } from "@octokit/plugin-retry"; +import { Credentials } from "./common/authentication"; const GITHUB_AUTH_PROVIDER_ID = "github"; @@ -13,41 +14,12 @@ const SCOPES = ["repo", "gist", "read:packages"]; /** * Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication). */ -export class Credentials { +export class VSCodeCredentials implements Credentials { /** * A specific octokit to return, otherwise a new authenticated octokit will be created when needed. */ private octokit: Octokit.Octokit | undefined; - // Explicitly make the constructor private, so that we can't accidentally call the constructor from outside the class - // without also initializing the class. - private constructor(octokit?: Octokit.Octokit) { - this.octokit = octokit; - } - - /** - * Initializes a Credentials instance. This will generate octokit instances - * authenticated as the user. If there is not already an authenticated GitHub - * session available then the user will be prompted to log in. - * - * @returns An instance of credentials. - */ - static async initialize(): Promise { - return new Credentials(); - } - - /** - * Initializes an instance of credentials with an octokit instance using - * a specific known token. This method is meant to be used in - * non-interactive environments such as tests. - * - * @param overrideToken The GitHub token to use for authentication. - * @returns An instance of credentials. - */ - static async initializeWithToken(overrideToken: string) { - return new Credentials(new Octokit.Octokit({ auth: overrideToken, retry })); - } - /** * Creates or returns an instance of Octokit. * diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index 93bba9a1c..2b5cbd824 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -27,7 +27,7 @@ import { Logger, ProgressReporter } from "./common"; import { CompilationMessage } from "./pure/legacy-messages"; import { sarifParser } from "./sarif-parser"; import { dbSchemeToLanguage, walkDirectory } from "./helpers"; -import { Credentials } from "./authentication"; +import { App } from "./common/app"; /** * The version of the SARIF format that we are using. @@ -197,6 +197,7 @@ export class CodeQLCliServer implements Disposable { public quiet = false; constructor( + private readonly app: App, private distributionProvider: DistributionProvider, private cliConfig: CliConfig, private logger: Logger, @@ -618,9 +619,7 @@ export class CodeQLCliServer implements Disposable { addFormat = true, progressReporter?: ProgressReporter, ): Promise { - const credentials = await Credentials.initialize(); - - const accessToken = await credentials.getExistingAccessToken(); + const accessToken = await this.app.credentials.getExistingAccessToken(); const extraArgs = accessToken ? ["--github-auth-stdin"] : []; @@ -633,7 +632,7 @@ export class CodeQLCliServer implements Disposable { async (line) => { if (line.startsWith("Enter value for --github-auth-stdin")) { try { - return await credentials.getAccessToken(); + return await this.app.credentials.getAccessToken(); } catch (e) { // If the user cancels the authentication prompt, we still need to give a value to the CLI. // By giving a potentially invalid value, the user will just get a 401/403 when they try to access a diff --git a/extensions/ql-vscode/src/common/app.ts b/extensions/ql-vscode/src/common/app.ts index 3e5ae594c..67506a5fa 100644 --- a/extensions/ql-vscode/src/common/app.ts +++ b/extensions/ql-vscode/src/common/app.ts @@ -1,3 +1,4 @@ +import { Credentials } from "./authentication"; import { Disposable } from "../pure/disposable-object"; import { AppEventEmitter } from "./events"; import { Logger } from "./logging"; @@ -13,6 +14,7 @@ export interface App { readonly globalStoragePath: string; readonly workspaceStoragePath?: string; readonly workspaceState: Memento; + readonly credentials: Credentials; } export enum AppMode { diff --git a/extensions/ql-vscode/src/common/authentication.ts b/extensions/ql-vscode/src/common/authentication.ts new file mode 100644 index 000000000..00f2403b8 --- /dev/null +++ b/extensions/ql-vscode/src/common/authentication.ts @@ -0,0 +1,14 @@ +import * as Octokit from "@octokit/rest"; + +export interface Credentials { + /** + * Creates or returns an instance of Octokit. + * + * @returns An instance of Octokit. + */ + getOctokit(): Promise; + + getAccessToken(): Promise; + + getExistingAccessToken(): Promise; +} diff --git a/extensions/ql-vscode/src/common/vscode/vscode-app.ts b/extensions/ql-vscode/src/common/vscode/vscode-app.ts index 46813ac1c..8f54341b6 100644 --- a/extensions/ql-vscode/src/common/vscode/vscode-app.ts +++ b/extensions/ql-vscode/src/common/vscode/vscode-app.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import { VSCodeCredentials } from "../../authentication"; import { Disposable } from "../../pure/disposable-object"; import { App, AppMode } from "../app"; import { AppEventEmitter } from "../events"; @@ -7,9 +8,13 @@ import { Memento } from "../memento"; import { VSCodeAppEventEmitter } from "./events"; export class ExtensionApp implements App { + public readonly credentials: VSCodeCredentials; + public constructor( public readonly extensionContext: vscode.ExtensionContext, - ) {} + ) { + this.credentials = new VSCodeCredentials(); + } public get extensionPath(): string { return this.extensionContext.extensionPath; diff --git a/extensions/ql-vscode/src/databaseFetcher.ts b/extensions/ql-vscode/src/databaseFetcher.ts index d3280bea1..aba73d313 100644 --- a/extensions/ql-vscode/src/databaseFetcher.ts +++ b/extensions/ql-vscode/src/databaseFetcher.ts @@ -20,12 +20,12 @@ import { DatabaseManager, DatabaseItem } from "./databases"; import { showAndLogInformationMessage, tmpDir } from "./helpers"; import { reportStreamProgress, ProgressCallback } from "./commandRunner"; import { extLogger } from "./common"; -import { Credentials } from "./authentication"; import { getErrorMessage } from "./pure/helpers-pure"; import { getNwoFromGitHubUrl, isValidGitHubNwo, } from "./common/github-url-identifier-helper"; +import { Credentials } from "./common/authentication"; /** * Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file. diff --git a/extensions/ql-vscode/src/databases-ui.ts b/extensions/ql-vscode/src/databases-ui.ts index 80f7ac2de..725c5e808 100644 --- a/extensions/ql-vscode/src/databases-ui.ts +++ b/extensions/ql-vscode/src/databases-ui.ts @@ -35,9 +35,10 @@ import { promptImportInternetDatabase, } from "./databaseFetcher"; import { asyncFilter, getErrorMessage } from "./pure/helpers-pure"; -import { Credentials } from "./authentication"; import { QueryRunner } from "./queryRunner"; import { isCanary } from "./config"; +import { App } from "./common/app"; +import { Credentials } from "./common/authentication"; type ThemableIconPath = { light: string; dark: string } | string; @@ -220,11 +221,11 @@ export class DatabaseUI extends DisposableObject { private treeDataProvider: DatabaseTreeDataProvider; public constructor( + private app: App, private databaseManager: DatabaseManager, private readonly queryServer: QueryRunner | undefined, private readonly storagePath: string, readonly extensionPath: string, - private readonly getCredentials: () => Promise, ) { super(); @@ -297,9 +298,7 @@ export class DatabaseUI extends DisposableObject { commandRunnerWithProgress( "codeQLDatabases.chooseDatabaseGithub", async (progress: ProgressCallback, token: CancellationToken) => { - const credentials = isCanary() - ? await this.getCredentials() - : undefined; + const credentials = isCanary() ? this.app.credentials : undefined; await this.handleChooseDatabaseGithub(credentials, progress, token); }, { diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index a26ddc209..bb461deca 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -102,7 +102,6 @@ import { } from "./commandRunner"; import { CodeQlStatusBarHandler } from "./status-bar"; -import { Credentials } from "./authentication"; import { RemoteQueriesManager } from "./remote-queries/remote-queries-manager"; import { RemoteQueryResult } from "./remote-queries/remote-query-result"; import { URLSearchParams } from "url"; @@ -546,6 +545,8 @@ async function activateWithInstalledDistribution( // of activation. errorStubs.forEach((stub) => stub.dispose()); + const app = new ExtensionApp(ctx); + void extLogger.log("Initializing configuration listener..."); const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener( @@ -555,6 +556,7 @@ async function activateWithInstalledDistribution( void extLogger.log("Initializing CodeQL cli server..."); const cliServer = new CodeQLCliServer( + app, distributionManager, new CliConfigListener(), extLogger, @@ -587,11 +589,11 @@ async function activateWithInstalledDistribution( ctx.subscriptions.push(dbm); void extLogger.log("Initializing database panel."); const databaseUI = new DatabaseUI( + app, dbm, qs, getContextStoragePath(ctx), ctx.extensionPath, - () => Credentials.initialize(), ); databaseUI.init(); ctx.subscriptions.push(databaseUI); @@ -623,8 +625,6 @@ async function activateWithInstalledDistribution( void extLogger.log("Initializing variant analysis manager."); - const app = new ExtensionApp(ctx); - const dbModule = await DbModule.initialize(app); const variantAnalysisStorageDir = join( @@ -633,12 +633,14 @@ async function activateWithInstalledDistribution( ); await ensureDir(variantAnalysisStorageDir); const variantAnalysisResultsManager = new VariantAnalysisResultsManager( + app.credentials, cliServer, extLogger, ); const variantAnalysisManager = new VariantAnalysisManager( ctx, + app, cliServer, variantAnalysisStorageDir, variantAnalysisResultsManager, @@ -656,6 +658,7 @@ async function activateWithInstalledDistribution( void extLogger.log("Initializing remote queries manager."); const rqm = new RemoteQueriesManager( ctx, + app, cliServer, queryStorageDir, extLogger, @@ -664,6 +667,7 @@ async function activateWithInstalledDistribution( void extLogger.log("Initializing query history."); const qhm = new QueryHistoryManager( + app, qs, dbm, localQueryResultsView, @@ -1245,7 +1249,7 @@ async function activateWithInstalledDistribution( commandRunner( "codeQL.exportRemoteQueryResults", async (queryId: string) => { - await exportRemoteQueryResults(qhm, rqm, queryId); + await exportRemoteQueryResults(qhm, rqm, queryId, app.credentials); }, ), ); @@ -1263,6 +1267,7 @@ async function activateWithInstalledDistribution( variantAnalysisManager, variantAnalysisId, filterSort, + app.credentials, progress, token, ); @@ -1363,9 +1368,7 @@ async function activateWithInstalledDistribution( commandRunnerWithProgress( "codeQL.chooseDatabaseGithub", async (progress: ProgressCallback, token: CancellationToken) => { - const credentials = isCanary() - ? await Credentials.initialize() - : undefined; + const credentials = isCanary() ? app.credentials : undefined; await databaseUI.handleChooseDatabaseGithub( credentials, progress, @@ -1419,8 +1422,7 @@ async function activateWithInstalledDistribution( * Credentials for authenticating to GitHub. * These are used when making API calls. */ - const credentials = await Credentials.initialize(); - const octokit = await credentials.getOctokit(); + const octokit = await app.credentials.getOctokit(); const userInfo = await octokit.users.getAuthenticated(); void showAndLogInformationMessage( `Authenticated to GitHub as user: ${userInfo.data.login}`, diff --git a/extensions/ql-vscode/src/query-history.ts b/extensions/ql-vscode/src/query-history.ts index 6adecf535..ce4ab3cdc 100644 --- a/extensions/ql-vscode/src/query-history.ts +++ b/extensions/ql-vscode/src/query-history.ts @@ -55,7 +55,6 @@ import { import { pathExists } from "fs-extra"; import { CliVersionConstraint } from "./cli"; import { HistoryItemLabelProvider } from "./history-item-label-provider"; -import { Credentials } from "./authentication"; import { cancelRemoteQuery } from "./remote-queries/gh-api/gh-actions-api-client"; import { RemoteQueriesManager } from "./remote-queries/remote-queries-manager"; import { RemoteQueryHistoryItem } from "./remote-queries/remote-query-history-item"; @@ -69,6 +68,7 @@ import { QueryRunner } from "./queryRunner"; import { VariantAnalysisManager } from "./remote-queries/variant-analysis-manager"; import { VariantAnalysisHistoryItem } from "./remote-queries/variant-analysis-history-item"; import { getTotalResultCount } from "./remote-queries/shared/variant-analysis"; +import { App } from "./common/app"; /** * query-history.ts @@ -394,6 +394,7 @@ export class QueryHistoryManager extends DisposableObject { readonly onDidCompleteQuery = this._onDidCompleteQuery.event; constructor( + private readonly app: App, private readonly qs: QueryRunner, private readonly dbm: DatabaseManager, private readonly localQueriesResultsView: ResultsView, @@ -636,10 +637,6 @@ export class QueryHistoryManager extends DisposableObject { this._onDidCompleteQuery.fire(info); } - private getCredentials() { - return Credentials.initialize(); - } - /** * Register and create the history scrubber. */ @@ -1346,8 +1343,7 @@ export class QueryHistoryManager extends DisposableObject { void showAndLogInformationMessage( "Cancelling variant analysis. This may take a while.", ); - const credentials = await this.getCredentials(); - await cancelRemoteQuery(credentials, item.remoteQuery); + await cancelRemoteQuery(this.app.credentials, item.remoteQuery); } else if (item.t === "variant-analysis") { await commands.executeCommand( "codeQL.cancelVariantAnalysis", diff --git a/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts b/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts index 04f94d68a..25d5aef9f 100644 --- a/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts @@ -3,7 +3,6 @@ import { EOL } from "os"; import { extname } from "path"; import { CancellationToken } from "vscode"; -import { Credentials } from "../authentication"; import { Logger } from "../common"; import { downloadArtifactFromLink } from "./gh-api/gh-actions-api-client"; import { AnalysisSummary } from "./shared/remote-query-result"; @@ -19,6 +18,7 @@ import { CodeQLCliServer } from "../cli"; import { extractRawResults } from "./bqrs-processing"; import { asyncFilter, getErrorMessage } from "../pure/helpers-pure"; import { createDownloadPath } from "./download-link"; +import { App } from "../common/app"; export class AnalysesResultsManager { // Store for the results of various analyses for each remote query. @@ -26,6 +26,7 @@ export class AnalysesResultsManager { private readonly analysesResults: Map; constructor( + private readonly app: App, private readonly cliServer: CodeQLCliServer, readonly storagePath: string, private readonly logger: Logger, @@ -42,17 +43,11 @@ export class AnalysesResultsManager { return; } - const credentials = await Credentials.initialize(); - void this.logger.log( `Downloading and processing results for ${analysisSummary.nwo}`, ); - await this.downloadSingleAnalysisResults( - analysisSummary, - credentials, - publishResults, - ); + await this.downloadSingleAnalysisResults(analysisSummary, publishResults); } /** @@ -76,8 +71,6 @@ export class AnalysesResultsManager { (x) => !this.isAnalysisInMemory(x), ); - const credentials = await Credentials.initialize(); - void this.logger.log("Downloading and processing analyses results"); const batchSize = 3; @@ -94,11 +87,7 @@ export class AnalysesResultsManager { const batch = analysesToDownload.slice(i, i + batchSize); const batchTasks = batch.map((analysis) => - this.downloadSingleAnalysisResults( - analysis, - credentials, - publishResults, - ), + this.downloadSingleAnalysisResults(analysis, publishResults), ); const nwos = batch.map((a) => a.nwo).join(", "); @@ -138,7 +127,6 @@ export class AnalysesResultsManager { private async downloadSingleAnalysisResults( analysis: AnalysisSummary, - credentials: Credentials, publishResults: (analysesResults: AnalysisResults[]) => Promise, ): Promise { const analysisResults: AnalysisResults = { @@ -159,7 +147,7 @@ export class AnalysesResultsManager { let artifactPath; try { artifactPath = await downloadArtifactFromLink( - credentials, + this.app.credentials, this.storagePath, analysis.downloadLink, ); diff --git a/extensions/ql-vscode/src/remote-queries/export-results.ts b/extensions/ql-vscode/src/remote-queries/export-results.ts index e829889b5..d21153b2f 100644 --- a/extensions/ql-vscode/src/remote-queries/export-results.ts +++ b/extensions/ql-vscode/src/remote-queries/export-results.ts @@ -9,7 +9,6 @@ import { window, workspace, } from "vscode"; -import { Credentials } from "../authentication"; import { ProgressCallback, UserCancellationException } from "../commandRunner"; import { showInformationMessageWithAction } from "../helpers"; import { extLogger } from "../common"; @@ -37,6 +36,7 @@ import { filterAndSortRepositoriesWithResults, RepositoriesFilterSortStateWithIds, } from "../pure/variant-analysis-filter-sort"; +import { Credentials } from "../common/authentication"; /** * Exports the results of the currently-selected remote query or variant analysis. @@ -74,6 +74,7 @@ export async function exportRemoteQueryResults( queryHistoryManager: QueryHistoryManager, remoteQueriesManager: RemoteQueriesManager, queryId: string, + credentials: Credentials, ): Promise { const queryHistoryItem = queryHistoryManager.getRemoteQueryById(queryId); if (!queryHistoryItem) { @@ -109,6 +110,7 @@ export async function exportRemoteQueryResults( query, analysesResults, exportFormat, + credentials, ); } @@ -117,6 +119,7 @@ export async function exportRemoteQueryAnalysisResults( query: RemoteQuery, analysesResults: AnalysisResults[], exportFormat: "gist" | "local", + credentials: Credentials, ) { const description = buildGistDescription(query, analysesResults); const markdownFiles = generateMarkdown(query, analysesResults, exportFormat); @@ -126,6 +129,7 @@ export async function exportRemoteQueryAnalysisResults( description, markdownFiles, exportFormat, + credentials, ); } @@ -139,6 +143,7 @@ export async function exportVariantAnalysisResults( variantAnalysisManager: VariantAnalysisManager, variantAnalysisId: number, filterSort: RepositoriesFilterSortStateWithIds | undefined, + credentials: Credentials, progress: ProgressCallback, token: CancellationToken, ): Promise { @@ -238,6 +243,7 @@ export async function exportVariantAnalysisResults( getAnalysesResults(), repositories?.length ?? 0, exportFormat, + credentials, progress, token, ); @@ -251,6 +257,7 @@ export async function exportVariantAnalysisAnalysisResults( >, expectedAnalysesResultsCount: number, exportFormat: "gist" | "local", + credentials: Credentials, progress: ProgressCallback, token: CancellationToken, ) { @@ -280,6 +287,7 @@ export async function exportVariantAnalysisAnalysisResults( description, markdownFiles, exportFormat, + credentials, progress, token, ); @@ -323,6 +331,7 @@ export async function exportResults( description: string, markdownFiles: MarkdownFile[], exportFormat: "gist" | "local", + credentials: Credentials, progress?: ProgressCallback, token?: CancellationToken, ) { @@ -331,7 +340,13 @@ export async function exportResults( } if (exportFormat === "gist") { - await exportToGist(description, markdownFiles, progress, token); + await exportToGist( + description, + markdownFiles, + credentials, + progress, + token, + ); } else if (exportFormat === "local") { await exportToLocalMarkdown( exportedResultsPath, @@ -345,6 +360,7 @@ export async function exportResults( export async function exportToGist( description: string, markdownFiles: MarkdownFile[], + credentials: Credentials, progress?: ProgressCallback, token?: CancellationToken, ) { @@ -354,8 +370,6 @@ export async function exportToGist( message: "Creating Gist", }); - const credentials = await Credentials.initialize(); - if (token?.isCancellationRequested) { throw new UserCancellationException("Cancelled"); } diff --git a/extensions/ql-vscode/src/remote-queries/gh-api/gh-actions-api-client.ts b/extensions/ql-vscode/src/remote-queries/gh-api/gh-actions-api-client.ts index 244526358..ff16c8730 100644 --- a/extensions/ql-vscode/src/remote-queries/gh-api/gh-actions-api-client.ts +++ b/extensions/ql-vscode/src/remote-queries/gh-api/gh-actions-api-client.ts @@ -5,7 +5,7 @@ import { showAndLogWarningMessage, tmpDir, } from "../../helpers"; -import { Credentials } from "../../authentication"; +import { Credentials } from "../../common/authentication"; import { extLogger } from "../../common"; import { RemoteQueryWorkflowResult } from "../remote-query-workflow-result"; import { DownloadLink, createDownloadPath } from "../download-link"; 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 896104b3d..a80323e0d 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 @@ -1,5 +1,5 @@ -import { Credentials } from "../../authentication"; import { OctokitResponse } from "@octokit/types/dist-types"; +import { Credentials } from "../../common/authentication"; import { RemoteQueriesSubmission } from "../shared/remote-queries"; import { VariantAnalysisSubmission } from "../shared/variant-analysis"; import { diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-api.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-api.ts index 1b2a1fa48..d50aea236 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-api.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-api.ts @@ -1,5 +1,5 @@ import { EOL } from "os"; -import { Credentials } from "../authentication"; +import { Credentials } from "../common/authentication"; import { RepositorySelection } from "./repository-selection"; import { Repository } from "./shared/repository"; import { RemoteQueriesResponse } from "./gh-api/remote-queries"; 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 10c9925b3..9706cade2 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts @@ -11,7 +11,6 @@ import { join } from "path"; import { writeFile, readFile, remove, pathExists } from "fs-extra"; import { EOL } from "os"; -import { Credentials } from "../authentication"; import { CodeQLCliServer } from "../cli"; import { ProgressCallback } from "../commandRunner"; import { @@ -42,6 +41,7 @@ import { QueryStatus } from "../query-status"; import { DisposableObject } from "../pure/disposable-object"; import { AnalysisResults } from "./shared/analysis-result"; import { runRemoteQueriesApiRequest } from "./remote-queries-api"; +import { App } from "../common/app"; const autoDownloadMaxSize = 300 * 1024; const autoDownloadMaxCount = 100; @@ -82,12 +82,14 @@ export class RemoteQueriesManager extends DisposableObject { constructor( ctx: ExtensionContext, + private readonly app: App, private readonly cliServer: CodeQLCliServer, private readonly storagePath: string, logger: Logger, ) { super(); this.analysesResultsManager = new AnalysesResultsManager( + app, cliServer, storagePath, logger, @@ -159,8 +161,6 @@ export class RemoteQueriesManager extends DisposableObject { progress: ProgressCallback, token: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(); - const { actionBranch, base64Pack, @@ -172,14 +172,14 @@ export class RemoteQueriesManager extends DisposableObject { language, } = await prepareRemoteQueryRun( this.cliServer, - credentials, + this.app.credentials, uri, progress, token, ); const apiResponse = await runRemoteQueriesApiRequest( - credentials, + this.app.credentials, actionBranch, language, repoSelection, @@ -217,10 +217,9 @@ export class RemoteQueriesManager extends DisposableObject { remoteQuery: RemoteQuery, cancellationToken: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(); - const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery( remoteQuery, + this.app.credentials, cancellationToken, ); @@ -230,7 +229,6 @@ export class RemoteQueriesManager extends DisposableObject { await this.downloadAvailableResults( queryId, remoteQuery, - credentials, executionEndTime, ); } else if (queryWorkflowResult.status === "CompletedUnsuccessfully") { @@ -244,7 +242,6 @@ export class RemoteQueriesManager extends DisposableObject { await this.downloadAvailableResults( queryId, remoteQuery, - credentials, executionEndTime, ); void showAndLogInformationMessage("Variant analysis was cancelled"); @@ -267,7 +264,6 @@ export class RemoteQueriesManager extends DisposableObject { await this.downloadAvailableResults( queryId, remoteQuery, - credentials, executionEndTime, ); void showAndLogInformationMessage("Variant analysis was cancelled"); @@ -444,15 +440,14 @@ export class RemoteQueriesManager extends DisposableObject { private async downloadAvailableResults( queryId: string, remoteQuery: RemoteQuery, - credentials: Credentials, executionEndTime: number, ): Promise { - const resultIndex = await getRemoteQueryIndex(credentials, remoteQuery); + const resultIndex = await getRemoteQueryIndex( + this.app.credentials, + remoteQuery, + ); if (resultIndex) { - const metadata = await this.getRepositoriesMetadata( - resultIndex, - credentials, - ); + const metadata = await this.getRepositoriesMetadata(resultIndex); const queryResult = this.mapQueryResult( executionEndTime, resultIndex, @@ -494,12 +489,9 @@ export class RemoteQueriesManager extends DisposableObject { } } - private async getRepositoriesMetadata( - resultIndex: RemoteQueryResultIndex, - credentials: Credentials, - ) { + private async getRepositoriesMetadata(resultIndex: RemoteQueryResultIndex) { const nwos = resultIndex.successes.map((s) => s.nwo); - return await getRepositoriesMetadata(credentials, nwos); + return await getRepositoriesMetadata(this.app.credentials, nwos); } // Pulled from the analysis results manager, so that we can get access to diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts index dd8c0c921..75706b46f 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; -import { Credentials } from "../authentication"; import { Logger } from "../common"; +import { Credentials } from "../common/authentication"; import { sleep } from "../pure/time"; import { getWorkflowStatus, @@ -20,10 +20,9 @@ export class RemoteQueriesMonitor { public async monitorQuery( remoteQuery: RemoteQuery, + credentials: Credentials, cancellationToken: vscode.CancellationToken, ): Promise { - const credentials = await Credentials.initialize(); - let attemptCount = 0; while (attemptCount <= RemoteQueriesMonitor.maxAttemptCount) { 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 50146b750..8eca875d1 100644 --- a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts +++ b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts @@ -10,7 +10,7 @@ import { tryGetQueryMetadata, tmpDir, } from "../helpers"; -import { Credentials } from "../authentication"; +import { Credentials } from "../common/authentication"; import * as cli from "../cli"; import { extLogger } from "../common"; import { 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 38a39e5c4..b1243c380 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts @@ -16,7 +16,6 @@ import { workspace, } from "vscode"; import { DisposableObject } from "../pure/disposable-object"; -import { Credentials } from "../authentication"; import { VariantAnalysisMonitor } from "./variant-analysis-monitor"; import { getActionsWorkflowRunUrl, @@ -62,6 +61,7 @@ import { import { URLSearchParams } from "url"; import { DbManager } from "../databases/db-manager"; import { isVariantAnalysisReposPanelEnabled } from "../config"; +import { App } from "../common/app"; export class VariantAnalysisManager extends DisposableObject @@ -100,6 +100,7 @@ export class VariantAnalysisManager constructor( private readonly ctx: ExtensionContext, + private readonly app: App, private readonly cliServer: CodeQLCliServer, private readonly storagePath: string, private readonly variantAnalysisResultsManager: VariantAnalysisResultsManager, @@ -126,8 +127,6 @@ export class VariantAnalysisManager progress: ProgressCallback, token: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(); - const { actionBranch, base64Pack, @@ -139,7 +138,7 @@ export class VariantAnalysisManager language, } = await prepareRemoteQueryRun( this.cliServer, - credentials, + this.app.credentials, uri, progress, token, @@ -175,7 +174,7 @@ export class VariantAnalysisManager }; const variantAnalysisResponse = await submitVariantAnalysis( - credentials, + this.app.credentials, variantAnalysisSubmission, ); @@ -456,6 +455,7 @@ export class VariantAnalysisManager ): Promise { await this.variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + this.app.credentials, cancellationToken, ); } @@ -480,8 +480,6 @@ export class VariantAnalysisManager await this.onRepoStateUpdated(variantAnalysis.id, repoState); - const credentials = await Credentials.initialize(); - if (cancellationToken && cancellationToken.isCancellationRequested) { repoState.downloadStatus = VariantAnalysisScannedRepositoryDownloadStatus.Failed; @@ -492,7 +490,7 @@ export class VariantAnalysisManager let repoTask: VariantAnalysisRepositoryTask; try { const repoTaskResponse = await getVariantAnalysisRepo( - credentials, + this.app.credentials, variantAnalysis.controllerRepo.id, variantAnalysis.id, scannedRepo.repository.id, @@ -517,7 +515,6 @@ export class VariantAnalysisManager try { await this.variantAnalysisResultsManager.download( - credentials, variantAnalysis.id, repoTask, this.getVariantAnalysisStorageLocation(variantAnalysis.id), @@ -578,12 +575,10 @@ export class VariantAnalysisManager ); } - const credentials = await Credentials.initialize(); - void showAndLogInformationMessage( "Cancelling variant analysis. This may take a while.", ); - await cancelVariantAnalysis(credentials, variantAnalysis); + await cancelVariantAnalysis(this.app.credentials, variantAnalysis); } public async openVariantAnalysisLogs(variantAnalysisId: number) { diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts index cf9979c5e..f7683fb4b 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts @@ -1,5 +1,4 @@ import { CancellationToken, commands, EventEmitter } from "vscode"; -import { Credentials } from "../authentication"; import { getVariantAnalysis } from "./gh-api/gh-api-client"; import { @@ -14,6 +13,7 @@ import { DisposableObject } from "../pure/disposable-object"; import { sleep } from "../pure/time"; import { getErrorMessage } from "../pure/helpers-pure"; import { showAndLogWarningMessage } from "../helpers"; +import { Credentials } from "../common/authentication"; export class VariantAnalysisMonitor extends DisposableObject { // With a sleep of 5 seconds, the maximum number of attempts takes @@ -36,10 +36,9 @@ export class VariantAnalysisMonitor extends DisposableObject { public async monitorVariantAnalysis( variantAnalysis: VariantAnalysis, + credentials: Credentials, cancellationToken: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(); - let attemptCount = 0; const scannedReposDownloaded: number[] = []; 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 acae8c020..a8e7ce50a 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 @@ -8,7 +8,7 @@ import { import { EOL } from "os"; import { join } from "path"; -import { Credentials } from "../authentication"; +import { Credentials } from "../common/authentication"; import { Logger } from "../common"; import { AnalysisAlert, AnalysisRawResults } from "./shared/analysis-result"; import { sarifParser } from "../sarif-parser"; @@ -63,6 +63,7 @@ export class VariantAnalysisResultsManager extends DisposableObject { readonly onResultLoaded = this._onResultLoaded.event; constructor( + private readonly credentials: Credentials, private readonly cliServer: CodeQLCliServer, private readonly logger: Logger, ) { @@ -71,7 +72,6 @@ export class VariantAnalysisResultsManager extends DisposableObject { } public async download( - credentials: Credentials, variantAnalysisId: number, repoTask: VariantAnalysisRepositoryTask, variantAnalysisStoragePath: string, @@ -86,7 +86,7 @@ export class VariantAnalysisResultsManager extends DisposableObject { ); const result = await getVariantAnalysisRepoResult( - credentials, + this.credentials, repoTask.artifactUrl, ); diff --git a/extensions/ql-vscode/test/__mocks__/appMock.ts b/extensions/ql-vscode/test/__mocks__/appMock.ts index cb8dd52d4..e5f74be29 100644 --- a/extensions/ql-vscode/test/__mocks__/appMock.ts +++ b/extensions/ql-vscode/test/__mocks__/appMock.ts @@ -4,6 +4,8 @@ import { Memento } from "../../src/common/memento"; import { Disposable } from "../../src/pure/disposable-object"; import { createMockLogger } from "./loggerMock"; import { createMockMemento } from "../mock-memento"; +import { testCredentialsWithStub } from "../factories/authentication"; +import { Credentials } from "../../src/common/authentication"; export function createMockApp({ extensionPath = "/mock/extension/path", @@ -12,6 +14,7 @@ export function createMockApp({ createEventEmitter = () => new MockAppEventEmitter(), executeCommand = jest.fn(() => Promise.resolve()), workspaceState = createMockMemento(), + credentials = testCredentialsWithStub(), }: { extensionPath?: string; workspaceStoragePath?: string; @@ -19,6 +22,7 @@ export function createMockApp({ createEventEmitter?: () => AppEventEmitter; executeCommand?: () => Promise; workspaceState?: Memento; + credentials?: Credentials; }): App { return { mode: AppMode.Test, @@ -30,6 +34,7 @@ export function createMockApp({ workspaceState, createEventEmitter, executeCommand, + credentials, }; } diff --git a/extensions/ql-vscode/test/factories/authentication.ts b/extensions/ql-vscode/test/factories/authentication.ts new file mode 100644 index 000000000..affe67dbe --- /dev/null +++ b/extensions/ql-vscode/test/factories/authentication.ts @@ -0,0 +1,38 @@ +import { retry } from "@octokit/plugin-retry"; +import * as Octokit from "@octokit/rest"; +import { RequestInterface } from "@octokit/types/dist-types/RequestInterface"; + +import { Credentials } from "../../src/common/authentication"; + +function makeTestOctokit(octokit: Octokit.Octokit): Credentials { + return { + getOctokit: async () => octokit, + getAccessToken: async () => { + throw new Error("getAccessToken not supported by test credentials"); + }, + getExistingAccessToken: async () => { + throw new Error( + "getExistingAccessToken not supported by test credentials", + ); + }, + }; +} + +export function testCredentialsWithStub( + requestSpy?: jest.SpyInstance>, +): Credentials { + const request = + requestSpy ?? + jest.fn(async () => { + throw new Error("Tried to make HTTP request but no stub was provided"); + }); + return makeTestOctokit({ request } as any); +} + +export function testCredentialsWithUnauthenticatedOctokit(): Credentials { + return makeTestOctokit(new Octokit.Octokit({ retry })); +} + +export function testCredentialsWithToken(token: string): Credentials { + return makeTestOctokit(new Octokit.Octokit({ auth: token, retry })); +} diff --git a/extensions/ql-vscode/test/unit-tests/remote-queries/gh-api/gh-api-client.test.ts b/extensions/ql-vscode/test/unit-tests/remote-queries/gh-api/gh-api-client.test.ts index 930a12302..b0cbde369 100644 --- a/extensions/ql-vscode/test/unit-tests/remote-queries/gh-api/gh-api-client.test.ts +++ b/extensions/ql-vscode/test/unit-tests/remote-queries/gh-api/gh-api-client.test.ts @@ -1,6 +1,3 @@ -import { Octokit as Octokit_Octokit } from "@octokit/rest"; -import { retry } from "@octokit/plugin-retry"; - import { faker } from "@faker-js/faker"; import { @@ -10,17 +7,15 @@ import { getVariantAnalysisRepoResult, submitVariantAnalysis, } from "../../../../src/remote-queries/gh-api/gh-api-client"; -import { Credentials } from "../../../../src/authentication"; import { createMockSubmission } from "../../../factories/remote-queries/shared/variant-analysis-submission"; import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server"; import { response } from "../../../../src/mocks/scenarios/problem-query-success/0-getRepo.json"; import { response as variantAnalysisJson_response } from "../../../../src/mocks/scenarios/problem-query-success/1-submitVariantAnalysis.json"; import { response as variantAnalysisRepoJson_response } from "../../../../src/mocks/scenarios/problem-query-success/9-getVariantAnalysisRepo.json"; +import { testCredentialsWithUnauthenticatedOctokit } from "../../../factories/authentication"; -const mockCredentials = { - getOctokit: () => Promise.resolve(new Octokit_Octokit({ retry })), -} as unknown as Credentials; +const mockCredentials = testCredentialsWithUnauthenticatedOctokit(); const mockServer = new MockGitHubApiServer(); beforeAll(() => mockServer.startServer()); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts index 57267a29a..f298b266a 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts @@ -25,11 +25,12 @@ import { OutputChannelLogger } from "../../../../src/common"; import { RemoteQueriesSubmission } from "../../../../src/remote-queries/shared/remote-queries"; import { readBundledPack } from "../../utils/bundled-pack-helpers"; import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager"; -import { Credentials } from "../../../../src/authentication"; import { fixWorkspaceReferences, restoreWorkspaceReferences, } from "../global.helper"; +import { createMockApp } from "../../../__mocks__/appMock"; +import { App } from "../../../../src/common/app"; // up to 3 minutes per test jest.setTimeout(3 * 60 * 1000); @@ -50,6 +51,7 @@ describe("Remote queries", () => { >; let ctx: ExtensionContext; let logger: any; + let app: App; let remoteQueriesManager: RemoteQueriesManager; let originalDeps: Record | undefined; @@ -74,8 +76,10 @@ describe("Remote queries", () => { ctx = createMockExtensionContext(); logger = new OutputChannelLogger("test-logger"); + app = createMockApp({}); remoteQueriesManager = new RemoteQueriesManager( ctx, + app, cli, "fake-storage-dir", logger, @@ -104,14 +108,6 @@ describe("Remote queries", () => { "vscode-codeql": ["github/vscode-codeql"], }); - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: undefined, - }), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); - // Only new version support `${workspace}` in qlpack.yml originalDeps = await fixWorkspaceReferences( qlpackFileWithWorkspaceRefs, diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts index 808427a57..a98338fad 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts @@ -19,7 +19,6 @@ import { } from "../../../../src/config"; import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client"; import * as ghActionsApiClient from "../../../../src/remote-queries/gh-api/gh-actions-api-client"; -import { Credentials } from "../../../../src/authentication"; import * as fs from "fs-extra"; import { join } from "path"; @@ -58,12 +57,15 @@ import { SortKey, } from "../../../../src/pure/variant-analysis-filter-sort"; import { DbManager } from "../../../../src/databases/db-manager"; +import { App } from "../../../../src/common/app"; +import { createMockApp } from "../../../__mocks__/appMock"; // 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; @@ -91,12 +93,15 @@ describe("Variant Analysis Manager", () => { )! .activate(); cli = extension.cliServer; + app = createMockApp({}); variantAnalysisResultsManager = new VariantAnalysisResultsManager( + app.credentials, cli, extLogger, ); variantAnalysisManager = new VariantAnalysisManager( extension.ctx, + app, cli, storagePath, variantAnalysisResultsManager, @@ -127,14 +132,6 @@ describe("Variant Analysis Manager", () => { } beforeEach(async () => { - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); - // Should not have asked for a language showQuickPickSpy = jest .spyOn(window, "showQuickPick") @@ -349,14 +346,6 @@ describe("Variant Analysis Manager", () => { let repoStatesPath: string; beforeEach(async () => { - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); - const sourceFilePath = join( __dirname, "../data/variant-analysis-results.zip", @@ -612,16 +601,6 @@ describe("Variant Analysis Manager", () => { }); describe("enqueueDownload", () => { - beforeEach(async () => { - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); - }); - it("should pop download tasks off the queue", async () => { const getResultsSpy = jest .spyOn(variantAnalysisManager, "autoDownloadVariantAnalysisResult") @@ -782,8 +761,6 @@ describe("Variant Analysis Manager", () => { let variantAnalysisStorageLocation: string; - let mockCredentials: Credentials; - beforeEach(async () => { variantAnalysis = createMockVariantAnalysis({}); @@ -797,14 +774,6 @@ describe("Variant Analysis Manager", () => { ); await createTimestampFile(variantAnalysisStorageLocation); await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis); - - mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); }); afterEach(() => { @@ -842,7 +811,7 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id); expect(mockCancelVariantAnalysis).toBeCalledWith( - mockCredentials, + app.credentials, variantAnalysis, ); }); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts index 13b0bd83b..e8d1d9ec1 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts @@ -23,9 +23,9 @@ import { processScannedRepository, processUpdatedVariantAnalysis, } from "../../../../src/remote-queries/variant-analysis-processor"; -import { Credentials } from "../../../../src/authentication"; import { createMockVariantAnalysis } from "../../../factories/remote-queries/shared/variant-analysis"; import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager"; +import { testCredentialsWithStub } from "../../../factories/authentication"; jest.setTimeout(60_000); @@ -74,14 +74,6 @@ describe("Variant Analysis Monitor", () => { .mockRejectedValue(new Error("Not mocked")); limitNumberOfAttemptsToMonitor(); - - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); }); it("should return early if variant analysis is cancelled", async () => { @@ -89,6 +81,7 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -100,6 +93,7 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -117,6 +111,7 @@ describe("Variant Analysis Monitor", () => { it("should mark as failed and stop monitoring", async () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -166,6 +161,7 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -184,6 +180,7 @@ describe("Variant Analysis Monitor", () => { it("should download all available results", async () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -216,6 +213,7 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -225,6 +223,7 @@ describe("Variant Analysis Monitor", () => { it("should not try to download any repos", async () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -283,6 +282,7 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); @@ -301,6 +301,7 @@ describe("Variant Analysis Monitor", () => { it("should not try to download any repos", async () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, + testCredentialsWithStub(), cancellationTokenSource.token, ); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts index 7439cbf28..5da89124f 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts @@ -1,7 +1,6 @@ import { extensions } from "vscode"; import { CodeQLExtensionInterface } from "../../../../src/extension"; import { extLogger } from "../../../../src/common"; -import { Credentials } from "../../../../src/authentication"; import * as fs from "fs-extra"; import { join, resolve } from "path"; @@ -15,6 +14,8 @@ import { VariantAnalysisRepositoryTask, VariantAnalysisScannedRepositoryResult, } from "../../../../src/remote-queries/shared/variant-analysis"; +import { testCredentialsWithStub } from "../../../factories/authentication"; +import { Credentials } from "../../../../src/common/authentication"; jest.setTimeout(10_000); @@ -34,12 +35,6 @@ describe(VariantAnalysisResultsManager.name, () => { }); describe("download", () => { - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; let dummyRepoTask: VariantAnalysisRepositoryTask; let variantAnalysisStoragePath: string; let repoTaskStorageDirectory: string; @@ -49,6 +44,7 @@ describe(VariantAnalysisResultsManager.name, () => { jest.spyOn(extLogger, "log").mockResolvedValue(undefined); variantAnalysisResultsManager = new VariantAnalysisResultsManager( + testCredentialsWithStub(), cli, extLogger, ); @@ -90,7 +86,6 @@ describe(VariantAnalysisResultsManager.name, () => { await expect( variantAnalysisResultsManager.download( - mockCredentials, variantAnalysisId, dummyRepoTask, variantAnalysisStoragePath, @@ -127,7 +122,6 @@ describe(VariantAnalysisResultsManager.name, () => { it("should call the API to download the results", async () => { await variantAnalysisResultsManager.download( - mockCredentials, variantAnalysisId, dummyRepoTask, variantAnalysisStoragePath, @@ -138,7 +132,6 @@ describe(VariantAnalysisResultsManager.name, () => { it("should save the results zip file to disk", async () => { await variantAnalysisResultsManager.download( - mockCredentials, variantAnalysisId, dummyRepoTask, variantAnalysisStoragePath, @@ -151,7 +144,6 @@ describe(VariantAnalysisResultsManager.name, () => { it("should unzip the results in a `results/` folder", async () => { await variantAnalysisResultsManager.download( - mockCredentials, variantAnalysisId, dummyRepoTask, variantAnalysisStoragePath, @@ -165,7 +157,6 @@ describe(VariantAnalysisResultsManager.name, () => { describe("isVariantAnalysisRepoDownloaded", () => { it("should return true once results are downloaded", async () => { await variantAnalysisResultsManager.download( - mockCredentials, variantAnalysisId, dummyRepoTask, variantAnalysisStoragePath, @@ -194,6 +185,7 @@ describe(VariantAnalysisResultsManager.name, () => { beforeEach(() => { variantAnalysisResultsManager = new VariantAnalysisResultsManager( + testCredentialsWithStub(), cli, extLogger, ); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts index dd101daf8..6b59b3354 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts @@ -9,11 +9,8 @@ import { window, workspace, } from "vscode"; -import { Octokit } from "@octokit/rest"; -import { retry } from "@octokit/plugin-retry"; import { CodeQLExtensionInterface } from "../../../../src/extension"; -import { Credentials } from "../../../../src/authentication"; import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server"; jest.setTimeout(30_000); @@ -93,11 +90,6 @@ describe("Variant Analysis Submission Integration", () => { }, }); - const mockCredentials = { - getOctokit: () => Promise.resolve(new Octokit({ retry })), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); - quickPickSpy = jest .spyOn(window, "showQuickPick") .mockResolvedValue(undefined); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/databases-ui.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases-ui.test.ts index 84970df47..76246c48f 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/databases-ui.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases-ui.test.ts @@ -11,7 +11,7 @@ import { Uri } from "vscode"; import { DatabaseUI } from "../../../src/databases-ui"; import { testDisposeHandler } from "../test-dispose-handler"; -import { Credentials } from "../../../src/authentication"; +import { createMockApp } from "../../__mocks__/appMock"; describe("databases-ui", () => { describe("fixDbUri", () => { @@ -82,7 +82,9 @@ describe("databases-ui", () => { "codeql-database.yml", ); + const app = createMockApp({}); const databaseUI = new DatabaseUI( + app, { databaseItems: [{ databaseUri: Uri.file(db1) }], onDidChangeDatabaseItem: () => { @@ -95,7 +97,6 @@ describe("databases-ui", () => { {} as any, storageDir, storageDir, - () => Promise.resolve({} as Credentials), ); await databaseUI.handleRemoveOrphanedDatabases(); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history.test.ts index 00986b9dc..2ea887a54 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history.test.ts @@ -42,10 +42,12 @@ import { VariantAnalysisHistoryItem } from "../../../src/remote-queries/variant- import { QueryStatus } from "../../../src/query-status"; import { VariantAnalysisStatus } from "../../../src/remote-queries/shared/variant-analysis"; import * as ghActionsApiClient from "../../../src/remote-queries/gh-api/gh-actions-api-client"; -import { Credentials } from "../../../src/authentication"; import { QuickPickItem, TextEditor } from "vscode"; import { WebviewReveal } from "../../../src/interface-utils"; import * as helpers from "../../../src/helpers"; +import { testCredentialsWithStub } from "../../factories/authentication"; +import { createMockApp } from "../../__mocks__/appMock"; +import { Credentials } from "../../../src/common/authentication"; describe("query-history", () => { const mockExtensionLocation = join(tmpDir.name, "mock-extension-location"); @@ -873,23 +875,13 @@ describe("query-history", () => { }); describe("handleCancel", () => { - let mockCredentials: Credentials; let mockCancelRemoteQuery: jest.SpiedFunction< typeof ghActionsApiClient.cancelRemoteQuery >; const getOctokitStub = jest.fn(); + const mockCredentials = testCredentialsWithStub(getOctokitStub); beforeEach(async () => { - mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: getOctokitStub, - }), - } as unknown as Credentials; - jest - .spyOn(Credentials, "initialize") - .mockResolvedValue(mockCredentials); - mockCancelRemoteQuery = jest .spyOn(ghActionsApiClient, "cancelRemoteQuery") .mockResolvedValue(); @@ -897,7 +889,10 @@ describe("query-history", () => { describe("if the item is in progress", () => { it("should cancel a single local query", async () => { - queryHistoryManager = await createMockQueryHistory(localQueryHistory); + queryHistoryManager = await createMockQueryHistory( + localQueryHistory, + mockCredentials, + ); // cancelling the selected item const inProgress1 = localQueryHistory[4]; @@ -908,7 +903,10 @@ describe("query-history", () => { }); it("should cancel multiple local queries", async () => { - queryHistoryManager = await createMockQueryHistory(localQueryHistory); + queryHistoryManager = await createMockQueryHistory( + localQueryHistory, + mockCredentials, + ); // cancelling the selected item const inProgress1 = localQueryHistory[4]; @@ -926,7 +924,10 @@ describe("query-history", () => { }); it("should cancel a single remote query", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const inProgress1 = remoteQueryHistory[2]; @@ -939,7 +940,10 @@ describe("query-history", () => { }); it("should cancel multiple remote queries", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const inProgress1 = remoteQueryHistory[2]; @@ -960,7 +964,10 @@ describe("query-history", () => { }); it("should cancel a single variant analysis", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const inProgress1 = variantAnalysisHistory[1]; @@ -973,7 +980,10 @@ describe("query-history", () => { }); it("should cancel multiple variant analyses", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const inProgress1 = variantAnalysisHistory[1]; @@ -996,7 +1006,10 @@ describe("query-history", () => { describe("if the item is not in progress", () => { it("should not cancel a single local query", async () => { - queryHistoryManager = await createMockQueryHistory(localQueryHistory); + queryHistoryManager = await createMockQueryHistory( + localQueryHistory, + mockCredentials, + ); // cancelling the selected item const completed = localQueryHistory[0]; @@ -1007,7 +1020,10 @@ describe("query-history", () => { }); it("should not cancel multiple local queries", async () => { - queryHistoryManager = await createMockQueryHistory(localQueryHistory); + queryHistoryManager = await createMockQueryHistory( + localQueryHistory, + mockCredentials, + ); // cancelling the selected item const completed = localQueryHistory[0]; @@ -1025,7 +1041,10 @@ describe("query-history", () => { }); it("should not cancel a single remote query", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const completed = remoteQueryHistory[0]; @@ -1038,7 +1057,10 @@ describe("query-history", () => { }); it("should not cancel multiple remote queries", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const completed = remoteQueryHistory[0]; @@ -1059,7 +1081,10 @@ describe("query-history", () => { }); it("should not cancel a single variant analysis", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const completedVariantAnalysis = variantAnalysisHistory[0]; @@ -1074,7 +1099,10 @@ describe("query-history", () => { }); it("should not cancel multiple variant analyses", async () => { - queryHistoryManager = await createMockQueryHistory(allHistory); + queryHistoryManager = await createMockQueryHistory( + allHistory, + mockCredentials, + ); // cancelling the selected item const completedVariantAnalysis = variantAnalysisHistory[0]; @@ -1985,8 +2013,12 @@ describe("query-history", () => { }); }); - async function createMockQueryHistory(allHistory: QueryHistoryInfo[]) { + async function createMockQueryHistory( + allHistory: QueryHistoryInfo[], + credentials?: Credentials, + ) { const qhm = new QueryHistoryManager( + createMockApp({ credentials }), {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/export-results.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/export-results.test.ts index 28aad971e..9a7650e9e 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/export-results.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/export-results.test.ts @@ -1,17 +1,14 @@ import { join } from "path"; import { readFile } from "fs-extra"; -import { Credentials } from "../../../../src/authentication"; import * as markdownGenerator from "../../../../src/remote-queries/remote-queries-markdown-generation"; import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client"; import { exportRemoteQueryAnalysisResults } from "../../../../src/remote-queries/export-results"; +import { testCredentialsWithStub } from "../../../factories/authentication"; describe("export results", () => { describe("exportRemoteQueryAnalysisResults", () => { - const mockCredentials = {} as unknown as Credentials; - beforeEach(() => { jest.spyOn(markdownGenerator, "generateMarkdown").mockReturnValue([]); - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); }); it("should call the GitHub Actions API with the correct gist title", async function () { @@ -43,6 +40,7 @@ describe("export results", () => { query, analysesResults, "gist", + testCredentialsWithStub(), ); expect(mockCreateGist).toHaveBeenCalledTimes(1); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts index d2de53e68..9c419226c 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts @@ -1,4 +1,3 @@ -import { Credentials } from "../../../../../src/authentication"; import { cancelRemoteQuery, cancelVariantAnalysis, @@ -7,17 +6,16 @@ import { import { RemoteQuery } from "../../../../../src/remote-queries/remote-query"; import { createMockVariantAnalysis } from "../../../../factories/remote-queries/shared/variant-analysis"; import { VariantAnalysis } from "../../../../../src/remote-queries/shared/variant-analysis"; +import { + testCredentialsWithStub, + testCredentialsWithToken, +} from "../../../../factories/authentication"; jest.setTimeout(10000); describe("gh-actions-api-client mock responses", () => { const mockRequest = jest.fn(); - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: mockRequest, - }), - } as unknown as Credentials; + const mockCredentials = testCredentialsWithStub(mockRequest); describe("cancelRemoteQuery", () => { it("should cancel a remote query", async () => { @@ -95,7 +93,7 @@ describe("gh-actions-api-client real responses", () => { return; } - const credentials = await Credentials.initializeWithToken( + const credentials = testCredentialsWithToken( process.env.VSCODE_CODEQL_GITHUB_TOKEN!, ); const stargazers = await getRepositoriesMetadata( diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts index 38921ccf9..439b622fb 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts @@ -21,7 +21,6 @@ import { QueryHistoryConfig } from "../../../../src/config"; import { DatabaseManager } from "../../../../src/databases"; import { tmpDir, walkDirectory } from "../../../../src/helpers"; import { QueryHistoryManager } from "../../../../src/query-history"; -import { Credentials } from "../../../../src/authentication"; import { AnalysesResultsManager } from "../../../../src/remote-queries/analyses-results-manager"; import { RemoteQueryResult } from "../../../../src/remote-queries/shared/remote-query-result"; import { DisposableBucket } from "../../disposable-bucket"; @@ -32,6 +31,9 @@ import { ResultsView } from "../../../../src/interface"; import { EvalLogViewer } from "../../../../src/eval-log-viewer"; import { QueryRunner } from "../../../../src/queryRunner"; import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager"; +import { App } from "../../../../src/common/app"; +import { createMockApp } from "../../../__mocks__/appMock"; +import { testCredentialsWithStub } from "../../../factories/authentication"; // set a higher timeout since recursive delete may take a while, expecially on Windows. jest.setTimeout(120000); @@ -47,6 +49,8 @@ describe("Remote queries and query history manager", () => { /** noop */ }; + const mockOctokit = jest.fn(); + let app: App; let qhm: QueryHistoryManager; const localQueriesResultsViewStub = { showResults: jest.fn(), @@ -107,7 +111,9 @@ describe("Remote queries and query history manager", () => { ), ); + app = createMockApp({ credentials: testCredentialsWithStub(mockOctokit) }); qhm = new QueryHistoryManager( + app, {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub, @@ -256,19 +262,11 @@ describe("Remote queries and query history manager", () => { }); describe("AnalysisResultsManager", () => { - let mockCredentials: any; - let mockOctokit: any; let mockLogger: any; let mockCliServer: any; let arm: AnalysesResultsManager; beforeEach(() => { - mockOctokit = { - request: jest.fn(), - }; - mockCredentials = { - getOctokit: () => mockOctokit, - }; mockLogger = { log: jest.fn(), }; @@ -276,9 +274,9 @@ describe("Remote queries and query history manager", () => { bqrsInfo: jest.fn(), bqrsDecode: jest.fn(), }; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); arm = new AnalysesResultsManager( + app, mockCliServer, join(STORAGE_DIR, "queries"), mockLogger, @@ -292,7 +290,7 @@ describe("Remote queries and query history manager", () => { await arm.downloadAnalysisResults(analysisSummary, publisher); // Should not have made the request since the analysis result is already on disk - expect(mockOctokit.request).not.toBeCalled(); + expect(mockOctokit).not.toBeCalled(); // result should have been published twice expect(publisher).toHaveBeenCalledTimes(2); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts index 9d8f1bfcd..adc03a4f1 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts @@ -21,6 +21,8 @@ import { ResultsView } from "../../../../src/interface"; import { EvalLogViewer } from "../../../../src/eval-log-viewer"; import { QueryRunner } from "../../../../src/queryRunner"; import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager"; +import { App } from "../../../../src/common/app"; +import { createMockApp } from "../../../__mocks__/appMock"; // set a higher timeout since recursive delete may take a while, expecially on Windows. jest.setTimeout(120000); @@ -36,6 +38,7 @@ describe("Variant Analyses and QueryHistoryManager", () => { /** noop */ }; + let app: App; let qhm: QueryHistoryManager; let rawQueryHistory: any; let disposables: DisposableBucket; @@ -77,7 +80,10 @@ describe("Variant Analyses and QueryHistoryManager", () => { join(STORAGE_DIR, "workspace-query-history.json"), ).queries; + app = createMockApp({}); + qhm = new QueryHistoryManager( + app, {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub,