Merge pull request #1892 from github/robertbrignull/undefined_credentials
Simplify the credentials class, and clear up impossible error cases
This commit is contained in:
Коммит
25dd679b7d
|
@ -13,102 +13,59 @@ const SCOPES = ["repo", "gist"];
|
||||||
* Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication).
|
* Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication).
|
||||||
*/
|
*/
|
||||||
export class Credentials {
|
export class Credentials {
|
||||||
|
/**
|
||||||
|
* A specific octokit to return, otherwise a new authenticated octokit will be created when needed.
|
||||||
|
*/
|
||||||
private octokit: Octokit.Octokit | undefined;
|
private octokit: Octokit.Octokit | undefined;
|
||||||
|
|
||||||
// Explicitly make the constructor private, so that we can't accidentally call the constructor from outside the class
|
// Explicitly make the constructor private, so that we can't accidentally call the constructor from outside the class
|
||||||
// without also initializing the class.
|
// without also initializing the class.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
private constructor(octokit?: Octokit.Octokit) {
|
||||||
private constructor() {}
|
this.octokit = octokit;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes an instance of credentials with an octokit instance.
|
* 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.
|
||||||
*
|
*
|
||||||
* Do not call this method until you know you actually need an instance of credentials.
|
|
||||||
* since calling this method will require the user to log in.
|
|
||||||
*
|
|
||||||
* @param context The extension context.
|
|
||||||
* @returns An instance of credentials.
|
* @returns An instance of credentials.
|
||||||
*/
|
*/
|
||||||
static async initialize(
|
static async initialize(): Promise<Credentials> {
|
||||||
context: vscode.ExtensionContext,
|
return new Credentials();
|
||||||
): Promise<Credentials> {
|
|
||||||
const c = new Credentials();
|
|
||||||
c.registerListeners(context);
|
|
||||||
c.octokit = await c.createOctokit(false);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes an instance of credentials with an octokit instance using
|
* Initializes an instance of credentials with an octokit instance using
|
||||||
* a token from the user's GitHub account. This method is meant to be
|
* a specific known token. This method is meant to be used in
|
||||||
* used non-interactive environments such as tests.
|
* non-interactive environments such as tests.
|
||||||
*
|
*
|
||||||
* @param overrideToken The GitHub token to use for authentication.
|
* @param overrideToken The GitHub token to use for authentication.
|
||||||
* @returns An instance of credentials.
|
* @returns An instance of credentials.
|
||||||
*/
|
*/
|
||||||
static async initializeWithToken(overrideToken: string) {
|
static async initializeWithToken(overrideToken: string) {
|
||||||
const c = new Credentials();
|
return new Credentials(new Octokit.Octokit({ auth: overrideToken, retry }));
|
||||||
c.octokit = await c.createOctokit(false, overrideToken);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createOctokit(
|
|
||||||
createIfNone: boolean,
|
|
||||||
overrideToken?: string,
|
|
||||||
): Promise<Octokit.Octokit | undefined> {
|
|
||||||
if (overrideToken) {
|
|
||||||
return new Octokit.Octokit({ auth: overrideToken, retry });
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await vscode.authentication.getSession(
|
|
||||||
GITHUB_AUTH_PROVIDER_ID,
|
|
||||||
SCOPES,
|
|
||||||
{ createIfNone },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
return new Octokit.Octokit({
|
|
||||||
auth: session.accessToken,
|
|
||||||
retry,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerListeners(context: vscode.ExtensionContext): void {
|
|
||||||
// Sessions are changed when a user logs in or logs out.
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.authentication.onDidChangeSessions(async (e) => {
|
|
||||||
if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) {
|
|
||||||
this.octokit = await this.createOctokit(false);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates or returns an instance of Octokit.
|
* Creates or returns an instance of Octokit.
|
||||||
*
|
*
|
||||||
* @param requireAuthentication Whether the Octokit instance needs to be authenticated as user.
|
|
||||||
* @returns An instance of Octokit.
|
* @returns An instance of Octokit.
|
||||||
*/
|
*/
|
||||||
async getOctokit(requireAuthentication = true): Promise<Octokit.Octokit> {
|
async getOctokit(): Promise<Octokit.Octokit> {
|
||||||
if (this.octokit) {
|
if (this.octokit) {
|
||||||
return this.octokit;
|
return this.octokit;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.octokit = await this.createOctokit(requireAuthentication);
|
const session = await vscode.authentication.getSession(
|
||||||
|
GITHUB_AUTH_PROVIDER_ID,
|
||||||
|
SCOPES,
|
||||||
|
{ createIfNone: true },
|
||||||
|
);
|
||||||
|
|
||||||
if (!this.octokit) {
|
return new Octokit.Octokit({
|
||||||
if (requireAuthentication) {
|
auth: session.accessToken,
|
||||||
throw new Error("Did not initialize Octokit.");
|
retry,
|
||||||
}
|
});
|
||||||
|
|
||||||
// We don't want to set this in this.octokit because that would prevent
|
|
||||||
// authenticating when requireCredentials is true.
|
|
||||||
return new Octokit.Octokit({ retry });
|
|
||||||
}
|
|
||||||
return this.octokit;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ export async function promptImportGithubDatabase(
|
||||||
}
|
}
|
||||||
|
|
||||||
const octokit = credentials
|
const octokit = credentials
|
||||||
? await credentials.getOctokit(true)
|
? await credentials.getOctokit()
|
||||||
: new Octokit.Octokit({ retry });
|
: new Octokit.Octokit({ retry });
|
||||||
|
|
||||||
const result = await convertGithubNwoToDatabaseUrl(nwo, octokit, progress);
|
const result = await convertGithubNwoToDatabaseUrl(nwo, octokit, progress);
|
||||||
|
|
|
@ -591,7 +591,7 @@ async function activateWithInstalledDistribution(
|
||||||
qs,
|
qs,
|
||||||
getContextStoragePath(ctx),
|
getContextStoragePath(ctx),
|
||||||
ctx.extensionPath,
|
ctx.extensionPath,
|
||||||
() => Credentials.initialize(ctx),
|
() => Credentials.initialize(),
|
||||||
);
|
);
|
||||||
databaseUI.init();
|
databaseUI.init();
|
||||||
ctx.subscriptions.push(databaseUI);
|
ctx.subscriptions.push(databaseUI);
|
||||||
|
@ -1236,7 +1236,7 @@ async function activateWithInstalledDistribution(
|
||||||
commandRunner(
|
commandRunner(
|
||||||
"codeQL.exportRemoteQueryResults",
|
"codeQL.exportRemoteQueryResults",
|
||||||
async (queryId: string) => {
|
async (queryId: string) => {
|
||||||
await exportRemoteQueryResults(qhm, rqm, ctx, queryId);
|
await exportRemoteQueryResults(qhm, rqm, queryId);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1251,7 +1251,6 @@ async function activateWithInstalledDistribution(
|
||||||
filterSort?: RepositoriesFilterSortStateWithIds,
|
filterSort?: RepositoriesFilterSortStateWithIds,
|
||||||
) => {
|
) => {
|
||||||
await exportVariantAnalysisResults(
|
await exportVariantAnalysisResults(
|
||||||
ctx,
|
|
||||||
variantAnalysisManager,
|
variantAnalysisManager,
|
||||||
variantAnalysisId,
|
variantAnalysisId,
|
||||||
filterSort,
|
filterSort,
|
||||||
|
@ -1356,7 +1355,7 @@ async function activateWithInstalledDistribution(
|
||||||
"codeQL.chooseDatabaseGithub",
|
"codeQL.chooseDatabaseGithub",
|
||||||
async (progress: ProgressCallback, token: CancellationToken) => {
|
async (progress: ProgressCallback, token: CancellationToken) => {
|
||||||
const credentials = isCanary()
|
const credentials = isCanary()
|
||||||
? await Credentials.initialize(ctx)
|
? await Credentials.initialize()
|
||||||
: undefined;
|
: undefined;
|
||||||
await databaseUI.handleChooseDatabaseGithub(
|
await databaseUI.handleChooseDatabaseGithub(
|
||||||
credentials,
|
credentials,
|
||||||
|
@ -1411,7 +1410,7 @@ async function activateWithInstalledDistribution(
|
||||||
* Credentials for authenticating to GitHub.
|
* Credentials for authenticating to GitHub.
|
||||||
* These are used when making API calls.
|
* These are used when making API calls.
|
||||||
*/
|
*/
|
||||||
const credentials = await Credentials.initialize(ctx);
|
const credentials = await Credentials.initialize();
|
||||||
const octokit = await credentials.getOctokit();
|
const octokit = await credentials.getOctokit();
|
||||||
const userInfo = await octokit.users.getAuthenticated();
|
const userInfo = await octokit.users.getAuthenticated();
|
||||||
void showAndLogInformationMessage(
|
void showAndLogInformationMessage(
|
||||||
|
|
|
@ -397,7 +397,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||||
private readonly variantAnalysisManager: VariantAnalysisManager,
|
private readonly variantAnalysisManager: VariantAnalysisManager,
|
||||||
private readonly evalLogViewer: EvalLogViewer,
|
private readonly evalLogViewer: EvalLogViewer,
|
||||||
private readonly queryStorageDir: string,
|
private readonly queryStorageDir: string,
|
||||||
private readonly ctx: ExtensionContext,
|
ctx: ExtensionContext,
|
||||||
private readonly queryHistoryConfigListener: QueryHistoryConfig,
|
private readonly queryHistoryConfigListener: QueryHistoryConfig,
|
||||||
private readonly labelProvider: HistoryItemLabelProvider,
|
private readonly labelProvider: HistoryItemLabelProvider,
|
||||||
private readonly doCompareCallback: (
|
private readonly doCompareCallback: (
|
||||||
|
@ -633,7 +633,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCredentials() {
|
private getCredentials() {
|
||||||
return Credentials.initialize(this.ctx);
|
return Credentials.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { pathExists } from "fs-extra";
|
import { pathExists } from "fs-extra";
|
||||||
import { EOL } from "os";
|
import { EOL } from "os";
|
||||||
import { extname } from "path";
|
import { extname } from "path";
|
||||||
import { CancellationToken, ExtensionContext } from "vscode";
|
import { CancellationToken } from "vscode";
|
||||||
|
|
||||||
import { Credentials } from "../authentication";
|
import { Credentials } from "../authentication";
|
||||||
import { Logger } from "../common";
|
import { Logger } from "../common";
|
||||||
|
@ -26,7 +26,6 @@ export class AnalysesResultsManager {
|
||||||
private readonly analysesResults: Map<string, AnalysisResults[]>;
|
private readonly analysesResults: Map<string, AnalysisResults[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly ctx: ExtensionContext,
|
|
||||||
private readonly cliServer: CodeQLCliServer,
|
private readonly cliServer: CodeQLCliServer,
|
||||||
readonly storagePath: string,
|
readonly storagePath: string,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
|
@ -43,7 +42,7 @@ export class AnalysesResultsManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await Credentials.initialize(this.ctx);
|
const credentials = await Credentials.initialize();
|
||||||
|
|
||||||
void this.logger.log(
|
void this.logger.log(
|
||||||
`Downloading and processing results for ${analysisSummary.nwo}`,
|
`Downloading and processing results for ${analysisSummary.nwo}`,
|
||||||
|
@ -77,7 +76,7 @@ export class AnalysesResultsManager {
|
||||||
(x) => !this.isAnalysisInMemory(x),
|
(x) => !this.isAnalysisInMemory(x),
|
||||||
);
|
);
|
||||||
|
|
||||||
const credentials = await Credentials.initialize(this.ctx);
|
const credentials = await Credentials.initialize();
|
||||||
|
|
||||||
void this.logger.log("Downloading and processing analyses results");
|
void this.logger.log("Downloading and processing analyses results");
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { ensureDir, writeFile } from "fs-extra";
|
||||||
import {
|
import {
|
||||||
commands,
|
commands,
|
||||||
CancellationToken,
|
CancellationToken,
|
||||||
ExtensionContext,
|
|
||||||
Uri,
|
Uri,
|
||||||
ViewColumn,
|
ViewColumn,
|
||||||
window,
|
window,
|
||||||
|
@ -74,7 +73,6 @@ export async function exportSelectedRemoteQueryResults(
|
||||||
export async function exportRemoteQueryResults(
|
export async function exportRemoteQueryResults(
|
||||||
queryHistoryManager: QueryHistoryManager,
|
queryHistoryManager: QueryHistoryManager,
|
||||||
remoteQueriesManager: RemoteQueriesManager,
|
remoteQueriesManager: RemoteQueriesManager,
|
||||||
ctx: ExtensionContext,
|
|
||||||
queryId: string,
|
queryId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const queryHistoryItem = queryHistoryManager.getRemoteQueryById(queryId);
|
const queryHistoryItem = queryHistoryManager.getRemoteQueryById(queryId);
|
||||||
|
@ -107,7 +105,6 @@ export async function exportRemoteQueryResults(
|
||||||
const exportedResultsDirectory = join(exportDirectory, "exported-results");
|
const exportedResultsDirectory = join(exportDirectory, "exported-results");
|
||||||
|
|
||||||
await exportRemoteQueryAnalysisResults(
|
await exportRemoteQueryAnalysisResults(
|
||||||
ctx,
|
|
||||||
exportedResultsDirectory,
|
exportedResultsDirectory,
|
||||||
query,
|
query,
|
||||||
analysesResults,
|
analysesResults,
|
||||||
|
@ -116,7 +113,6 @@ export async function exportRemoteQueryResults(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportRemoteQueryAnalysisResults(
|
export async function exportRemoteQueryAnalysisResults(
|
||||||
ctx: ExtensionContext,
|
|
||||||
exportedResultsPath: string,
|
exportedResultsPath: string,
|
||||||
query: RemoteQuery,
|
query: RemoteQuery,
|
||||||
analysesResults: AnalysisResults[],
|
analysesResults: AnalysisResults[],
|
||||||
|
@ -126,7 +122,6 @@ export async function exportRemoteQueryAnalysisResults(
|
||||||
const markdownFiles = generateMarkdown(query, analysesResults, exportFormat);
|
const markdownFiles = generateMarkdown(query, analysesResults, exportFormat);
|
||||||
|
|
||||||
await exportResults(
|
await exportResults(
|
||||||
ctx,
|
|
||||||
exportedResultsPath,
|
exportedResultsPath,
|
||||||
description,
|
description,
|
||||||
markdownFiles,
|
markdownFiles,
|
||||||
|
@ -141,7 +136,6 @@ const MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS = 2;
|
||||||
* The user is prompted to select the export format.
|
* The user is prompted to select the export format.
|
||||||
*/
|
*/
|
||||||
export async function exportVariantAnalysisResults(
|
export async function exportVariantAnalysisResults(
|
||||||
ctx: ExtensionContext,
|
|
||||||
variantAnalysisManager: VariantAnalysisManager,
|
variantAnalysisManager: VariantAnalysisManager,
|
||||||
variantAnalysisId: number,
|
variantAnalysisId: number,
|
||||||
filterSort: RepositoriesFilterSortStateWithIds | undefined,
|
filterSort: RepositoriesFilterSortStateWithIds | undefined,
|
||||||
|
@ -239,7 +233,6 @@ export async function exportVariantAnalysisResults(
|
||||||
);
|
);
|
||||||
|
|
||||||
await exportVariantAnalysisAnalysisResults(
|
await exportVariantAnalysisAnalysisResults(
|
||||||
ctx,
|
|
||||||
exportedResultsDirectory,
|
exportedResultsDirectory,
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
getAnalysesResults(),
|
getAnalysesResults(),
|
||||||
|
@ -251,7 +244,6 @@ export async function exportVariantAnalysisResults(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportVariantAnalysisAnalysisResults(
|
export async function exportVariantAnalysisAnalysisResults(
|
||||||
ctx: ExtensionContext,
|
|
||||||
exportedResultsPath: string,
|
exportedResultsPath: string,
|
||||||
variantAnalysis: VariantAnalysis,
|
variantAnalysis: VariantAnalysis,
|
||||||
analysesResults: AsyncIterable<
|
analysesResults: AsyncIterable<
|
||||||
|
@ -284,7 +276,6 @@ export async function exportVariantAnalysisAnalysisResults(
|
||||||
);
|
);
|
||||||
|
|
||||||
await exportResults(
|
await exportResults(
|
||||||
ctx,
|
|
||||||
exportedResultsPath,
|
exportedResultsPath,
|
||||||
description,
|
description,
|
||||||
markdownFiles,
|
markdownFiles,
|
||||||
|
@ -328,7 +319,6 @@ async function determineExportFormat(): Promise<"gist" | "local" | undefined> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportResults(
|
export async function exportResults(
|
||||||
ctx: ExtensionContext,
|
|
||||||
exportedResultsPath: string,
|
exportedResultsPath: string,
|
||||||
description: string,
|
description: string,
|
||||||
markdownFiles: MarkdownFile[],
|
markdownFiles: MarkdownFile[],
|
||||||
|
@ -341,7 +331,7 @@ export async function exportResults(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exportFormat === "gist") {
|
if (exportFormat === "gist") {
|
||||||
await exportToGist(ctx, description, markdownFiles, progress, token);
|
await exportToGist(description, markdownFiles, progress, token);
|
||||||
} else if (exportFormat === "local") {
|
} else if (exportFormat === "local") {
|
||||||
await exportToLocalMarkdown(
|
await exportToLocalMarkdown(
|
||||||
exportedResultsPath,
|
exportedResultsPath,
|
||||||
|
@ -353,7 +343,6 @@ export async function exportResults(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportToGist(
|
export async function exportToGist(
|
||||||
ctx: ExtensionContext,
|
|
||||||
description: string,
|
description: string,
|
||||||
markdownFiles: MarkdownFile[],
|
markdownFiles: MarkdownFile[],
|
||||||
progress?: ProgressCallback,
|
progress?: ProgressCallback,
|
||||||
|
@ -365,7 +354,7 @@ export async function exportToGist(
|
||||||
message: "Creating Gist",
|
message: "Creating Gist",
|
||||||
});
|
});
|
||||||
|
|
||||||
const credentials = await Credentials.initialize(ctx);
|
const credentials = await Credentials.initialize();
|
||||||
|
|
||||||
if (token?.isCancellationRequested) {
|
if (token?.isCancellationRequested) {
|
||||||
throw new UserCancellationException("Cancelled");
|
throw new UserCancellationException("Cancelled");
|
||||||
|
|
|
@ -81,20 +81,19 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||||
private readonly view: RemoteQueriesView;
|
private readonly view: RemoteQueriesView;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly ctx: ExtensionContext,
|
ctx: ExtensionContext,
|
||||||
private readonly cliServer: CodeQLCliServer,
|
private readonly cliServer: CodeQLCliServer,
|
||||||
private readonly storagePath: string,
|
private readonly storagePath: string,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.analysesResultsManager = new AnalysesResultsManager(
|
this.analysesResultsManager = new AnalysesResultsManager(
|
||||||
ctx,
|
|
||||||
cliServer,
|
cliServer,
|
||||||
storagePath,
|
storagePath,
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager);
|
this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager);
|
||||||
this.remoteQueriesMonitor = new RemoteQueriesMonitor(ctx, logger);
|
this.remoteQueriesMonitor = new RemoteQueriesMonitor(logger);
|
||||||
|
|
||||||
this.remoteQueryAddedEventEmitter = this.push(
|
this.remoteQueryAddedEventEmitter = this.push(
|
||||||
new EventEmitter<NewQueryEvent>(),
|
new EventEmitter<NewQueryEvent>(),
|
||||||
|
@ -160,7 +159,7 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const credentials = await Credentials.initialize(this.ctx);
|
const credentials = await Credentials.initialize();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actionBranch,
|
actionBranch,
|
||||||
|
@ -218,7 +217,7 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||||
remoteQuery: RemoteQuery,
|
remoteQuery: RemoteQuery,
|
||||||
cancellationToken: CancellationToken,
|
cancellationToken: CancellationToken,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const credentials = await Credentials.initialize(this.ctx);
|
const credentials = await Credentials.initialize();
|
||||||
|
|
||||||
const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery(
|
const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery(
|
||||||
remoteQuery,
|
remoteQuery,
|
||||||
|
|
|
@ -16,20 +16,13 @@ export class RemoteQueriesMonitor {
|
||||||
private static readonly maxAttemptCount = 17280;
|
private static readonly maxAttemptCount = 17280;
|
||||||
private static readonly sleepTime = 5000;
|
private static readonly sleepTime = 5000;
|
||||||
|
|
||||||
constructor(
|
constructor(private readonly logger: Logger) {}
|
||||||
private readonly extensionContext: vscode.ExtensionContext,
|
|
||||||
private readonly logger: Logger,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public async monitorQuery(
|
public async monitorQuery(
|
||||||
remoteQuery: RemoteQuery,
|
remoteQuery: RemoteQuery,
|
||||||
cancellationToken: vscode.CancellationToken,
|
cancellationToken: vscode.CancellationToken,
|
||||||
): Promise<RemoteQueryWorkflowResult> {
|
): Promise<RemoteQueryWorkflowResult> {
|
||||||
const credentials = await Credentials.initialize(this.extensionContext);
|
const credentials = await Credentials.initialize();
|
||||||
|
|
||||||
if (!credentials) {
|
|
||||||
throw Error("Error authenticating with GitHub");
|
|
||||||
}
|
|
||||||
|
|
||||||
let attemptCount = 0;
|
let attemptCount = 0;
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,6 @@ export class VariantAnalysisManager
|
||||||
super();
|
super();
|
||||||
this.variantAnalysisMonitor = this.push(
|
this.variantAnalysisMonitor = this.push(
|
||||||
new VariantAnalysisMonitor(
|
new VariantAnalysisMonitor(
|
||||||
ctx,
|
|
||||||
this.shouldCancelMonitorVariantAnalysis.bind(this),
|
this.shouldCancelMonitorVariantAnalysis.bind(this),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -125,7 +124,7 @@ export class VariantAnalysisManager
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const credentials = await Credentials.initialize(this.ctx);
|
const credentials = await Credentials.initialize();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actionBranch,
|
actionBranch,
|
||||||
|
@ -479,10 +478,7 @@ export class VariantAnalysisManager
|
||||||
|
|
||||||
await this.onRepoStateUpdated(variantAnalysis.id, repoState);
|
await this.onRepoStateUpdated(variantAnalysis.id, repoState);
|
||||||
|
|
||||||
const credentials = await Credentials.initialize(this.ctx);
|
const credentials = await Credentials.initialize();
|
||||||
if (!credentials) {
|
|
||||||
throw Error("Error authenticating with GitHub");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
||||||
repoState.downloadStatus =
|
repoState.downloadStatus =
|
||||||
|
@ -580,10 +576,7 @@ export class VariantAnalysisManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await Credentials.initialize(this.ctx);
|
const credentials = await Credentials.initialize();
|
||||||
if (!credentials) {
|
|
||||||
throw Error("Error authenticating with GitHub");
|
|
||||||
}
|
|
||||||
|
|
||||||
void showAndLogInformationMessage(
|
void showAndLogInformationMessage(
|
||||||
"Cancelling variant analysis. This may take a while.",
|
"Cancelling variant analysis. This may take a while.",
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { CancellationToken, commands, EventEmitter } from "vscode";
|
||||||
CancellationToken,
|
|
||||||
commands,
|
|
||||||
EventEmitter,
|
|
||||||
ExtensionContext,
|
|
||||||
} from "vscode";
|
|
||||||
import { Credentials } from "../authentication";
|
import { Credentials } from "../authentication";
|
||||||
import { getVariantAnalysis } from "./gh-api/gh-api-client";
|
import { getVariantAnalysis } from "./gh-api/gh-api-client";
|
||||||
|
|
||||||
|
@ -32,7 +27,6 @@ export class VariantAnalysisMonitor extends DisposableObject {
|
||||||
readonly onVariantAnalysisChange = this._onVariantAnalysisChange.event;
|
readonly onVariantAnalysisChange = this._onVariantAnalysisChange.event;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly extensionContext: ExtensionContext,
|
|
||||||
private readonly shouldCancelMonitor: (
|
private readonly shouldCancelMonitor: (
|
||||||
variantAnalysisId: number,
|
variantAnalysisId: number,
|
||||||
) => Promise<boolean>,
|
) => Promise<boolean>,
|
||||||
|
@ -44,10 +38,7 @@ export class VariantAnalysisMonitor extends DisposableObject {
|
||||||
variantAnalysis: VariantAnalysis,
|
variantAnalysis: VariantAnalysis,
|
||||||
cancellationToken: CancellationToken,
|
cancellationToken: CancellationToken,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const credentials = await Credentials.initialize(this.extensionContext);
|
const credentials = await Credentials.initialize();
|
||||||
if (!credentials) {
|
|
||||||
throw Error("Error authenticating with GitHub");
|
|
||||||
}
|
|
||||||
|
|
||||||
let attemptCount = 0;
|
let attemptCount = 0;
|
||||||
const scannedReposDownloaded: number[] = [];
|
const scannedReposDownloaded: number[] = [];
|
||||||
|
|
|
@ -142,6 +142,14 @@ describe("Variant Analysis Manager", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
writeFileStub.mockRestore();
|
writeFileStub.mockRestore();
|
||||||
|
|
||||||
|
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
|
// Should not have asked for a language
|
||||||
showQuickPickSpy = jest
|
showQuickPickSpy = jest
|
||||||
.spyOn(window, "showQuickPick")
|
.spyOn(window, "showQuickPick")
|
||||||
|
@ -367,269 +375,267 @@ describe("Variant Analysis Manager", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("autoDownloadVariantAnalysisResult", () => {
|
describe("autoDownloadVariantAnalysisResult", () => {
|
||||||
describe("when credentials are invalid", () => {
|
let arrayBuffer: ArrayBuffer;
|
||||||
|
|
||||||
|
let getVariantAnalysisRepoStub: jest.SpiedFunction<
|
||||||
|
typeof ghApiClient.getVariantAnalysisRepo
|
||||||
|
>;
|
||||||
|
let getVariantAnalysisRepoResultStub: jest.SpiedFunction<
|
||||||
|
typeof ghApiClient.getVariantAnalysisRepoResult
|
||||||
|
>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const mockCredentials = {
|
||||||
|
getOctokit: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
request: jest.fn(),
|
||||||
|
}),
|
||||||
|
} as unknown as Credentials;
|
||||||
|
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
|
||||||
|
|
||||||
|
const sourceFilePath = join(
|
||||||
|
__dirname,
|
||||||
|
"../../../../src/vscode-tests/cli-integration/data/variant-analysis-results.zip",
|
||||||
|
);
|
||||||
|
arrayBuffer = fs.readFileSync(sourceFilePath).buffer;
|
||||||
|
|
||||||
|
getVariantAnalysisRepoStub = jest.spyOn(
|
||||||
|
ghApiClient,
|
||||||
|
"getVariantAnalysisRepo",
|
||||||
|
);
|
||||||
|
getVariantAnalysisRepoResultStub = jest.spyOn(
|
||||||
|
ghApiClient,
|
||||||
|
"getVariantAnalysisRepoResult",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the artifact_url is missing", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest
|
const dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||||
.spyOn(Credentials, "initialize")
|
delete dummyRepoTask.artifact_url;
|
||||||
.mockResolvedValue(undefined as unknown as Credentials);
|
|
||||||
|
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||||
|
getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return early if credentials are wrong", async () => {
|
it("should not try to download the result", async () => {
|
||||||
try {
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
scannedRepos[0],
|
||||||
scannedRepos[0],
|
variantAnalysis,
|
||||||
variantAnalysis,
|
cancellationTokenSource.token,
|
||||||
cancellationTokenSource.token,
|
);
|
||||||
);
|
|
||||||
} catch (error: any) {
|
expect(getVariantAnalysisRepoResultStub).not.toHaveBeenCalled();
|
||||||
expect(error.message).toBe("Error authenticating with GitHub");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when credentials are valid", () => {
|
describe("when the artifact_url is present", () => {
|
||||||
let arrayBuffer: ArrayBuffer;
|
let dummyRepoTask: VariantAnalysisRepoTask;
|
||||||
|
|
||||||
let getVariantAnalysisRepoStub: jest.SpiedFunction<
|
|
||||||
typeof ghApiClient.getVariantAnalysisRepo
|
|
||||||
>;
|
|
||||||
let getVariantAnalysisRepoResultStub: jest.SpiedFunction<
|
|
||||||
typeof ghApiClient.getVariantAnalysisRepoResult
|
|
||||||
>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const mockCredentials = {
|
dummyRepoTask = createMockVariantAnalysisRepoTask();
|
||||||
getOctokit: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
request: jest.fn(),
|
|
||||||
}),
|
|
||||||
} as unknown as Credentials;
|
|
||||||
jest
|
|
||||||
.spyOn(Credentials, "initialize")
|
|
||||||
.mockResolvedValue(mockCredentials);
|
|
||||||
|
|
||||||
const sourceFilePath = join(
|
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
||||||
__dirname,
|
getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer);
|
||||||
"../../../../src/vscode-tests/cli-integration/data/variant-analysis-results.zip",
|
});
|
||||||
);
|
|
||||||
arrayBuffer = fs.readFileSync(sourceFilePath).buffer;
|
|
||||||
|
|
||||||
getVariantAnalysisRepoStub = jest.spyOn(
|
it("should return early if variant analysis is cancelled", async () => {
|
||||||
ghApiClient,
|
cancellationTokenSource.cancel();
|
||||||
"getVariantAnalysisRepo",
|
|
||||||
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
|
scannedRepos[0],
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
);
|
);
|
||||||
getVariantAnalysisRepoResultStub = jest.spyOn(
|
|
||||||
ghApiClient,
|
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
||||||
"getVariantAnalysisRepoResult",
|
});
|
||||||
|
|
||||||
|
it("should fetch a repo task", async () => {
|
||||||
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
|
scannedRepos[0],
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getVariantAnalysisRepoStub).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch a repo result", async () => {
|
||||||
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
|
scannedRepos[0],
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getVariantAnalysisRepoResultStub).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should skip the download if the repository has already been downloaded", async () => {
|
||||||
|
// First, do a download so it is downloaded. This avoids having to mock the repo states.
|
||||||
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
|
scannedRepos[0],
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
getVariantAnalysisRepoStub.mockClear();
|
||||||
|
|
||||||
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
|
scannedRepos[0],
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should write the repo state when the download is successful", async () => {
|
||||||
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
|
scannedRepos[0],
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(outputJsonStub).toHaveBeenCalledWith(
|
||||||
|
join(storagePath, variantAnalysis.id.toString(), "repo_states.json"),
|
||||||
|
{
|
||||||
|
[scannedRepos[0].repository.id]: {
|
||||||
|
repositoryId: scannedRepos[0].repository.id,
|
||||||
|
downloadStatus:
|
||||||
|
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the artifact_url is missing", () => {
|
it("should not write the repo state when the download fails", async () => {
|
||||||
beforeEach(async () => {
|
getVariantAnalysisRepoResultStub.mockRejectedValue(
|
||||||
const dummyRepoTask = createMockVariantAnalysisRepoTask();
|
new Error("Failed to download"),
|
||||||
delete dummyRepoTask.artifact_url;
|
);
|
||||||
|
|
||||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
await expect(
|
||||||
getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer);
|
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
});
|
|
||||||
|
|
||||||
it("should not try to download the result", async () => {
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[0],
|
scannedRepos[0],
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
cancellationTokenSource.token,
|
cancellationTokenSource.token,
|
||||||
);
|
),
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
expect(getVariantAnalysisRepoResultStub).not.toHaveBeenCalled();
|
expect(outputJsonStub).not.toHaveBeenCalled();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the artifact_url is present", () => {
|
it("should have a failed repo state when the repo task API fails", async () => {
|
||||||
let dummyRepoTask: VariantAnalysisRepoTask;
|
getVariantAnalysisRepoStub.mockRejectedValueOnce(
|
||||||
|
new Error("Failed to download"),
|
||||||
|
);
|
||||||
|
|
||||||
beforeEach(async () => {
|
await expect(
|
||||||
dummyRepoTask = createMockVariantAnalysisRepoTask();
|
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
|
|
||||||
getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask);
|
|
||||||
getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return early if variant analysis is cancelled", async () => {
|
|
||||||
cancellationTokenSource.cancel();
|
|
||||||
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[0],
|
scannedRepos[0],
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
cancellationTokenSource.token,
|
cancellationTokenSource.token,
|
||||||
);
|
),
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
expect(outputJsonStub).not.toHaveBeenCalled();
|
||||||
});
|
|
||||||
|
|
||||||
it("should fetch a repo task", async () => {
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
scannedRepos[1],
|
||||||
scannedRepos[0],
|
variantAnalysis,
|
||||||
variantAnalysis,
|
cancellationTokenSource.token,
|
||||||
cancellationTokenSource.token,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
expect(getVariantAnalysisRepoStub).toHaveBeenCalled();
|
expect(outputJsonStub).toHaveBeenCalledWith(
|
||||||
});
|
join(storagePath, variantAnalysis.id.toString(), "repo_states.json"),
|
||||||
|
{
|
||||||
it("should fetch a repo result", async () => {
|
[scannedRepos[0].repository.id]: {
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
repositoryId: scannedRepos[0].repository.id,
|
||||||
scannedRepos[0],
|
downloadStatus:
|
||||||
variantAnalysis,
|
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(getVariantAnalysisRepoResultStub).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should skip the download if the repository has already been downloaded", async () => {
|
|
||||||
// First, do a download so it is downloaded. This avoids having to mock the repo states.
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[0],
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
getVariantAnalysisRepoStub.mockClear();
|
|
||||||
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[0],
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should write the repo state when the download is successful", async () => {
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[0],
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(outputJsonStub).toHaveBeenCalledWith(
|
|
||||||
join(
|
|
||||||
storagePath,
|
|
||||||
variantAnalysis.id.toString(),
|
|
||||||
"repo_states.json",
|
|
||||||
),
|
|
||||||
{
|
|
||||||
[scannedRepos[0].repository.id]: {
|
|
||||||
repositoryId: scannedRepos[0].repository.id,
|
|
||||||
downloadStatus:
|
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
[scannedRepos[1].repository.id]: {
|
||||||
});
|
repositoryId: scannedRepos[1].repository.id,
|
||||||
|
downloadStatus:
|
||||||
|
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should not write the repo state when the download fails", async () => {
|
it("should have a failed repo state when the download fails", async () => {
|
||||||
getVariantAnalysisRepoResultStub.mockRejectedValue(
|
getVariantAnalysisRepoResultStub.mockRejectedValueOnce(
|
||||||
new Error("Failed to download"),
|
new Error("Failed to download"),
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
scannedRepos[0],
|
scannedRepos[0],
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
),
|
|
||||||
).rejects.toThrow();
|
|
||||||
|
|
||||||
expect(outputJsonStub).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have a failed repo state when the repo task API fails", async () => {
|
|
||||||
getVariantAnalysisRepoStub.mockRejectedValueOnce(
|
|
||||||
new Error("Failed to download"),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[0],
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
),
|
|
||||||
).rejects.toThrow();
|
|
||||||
|
|
||||||
expect(outputJsonStub).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[1],
|
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
cancellationTokenSource.token,
|
cancellationTokenSource.token,
|
||||||
);
|
),
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
expect(outputJsonStub).toHaveBeenCalledWith(
|
expect(outputJsonStub).not.toHaveBeenCalled();
|
||||||
join(
|
|
||||||
storagePath,
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
variantAnalysis.id.toString(),
|
scannedRepos[1],
|
||||||
"repo_states.json",
|
variantAnalysis,
|
||||||
),
|
cancellationTokenSource.token,
|
||||||
{
|
);
|
||||||
[scannedRepos[0].repository.id]: {
|
|
||||||
repositoryId: scannedRepos[0].repository.id,
|
expect(outputJsonStub).toHaveBeenCalledWith(
|
||||||
downloadStatus:
|
join(storagePath, variantAnalysis.id.toString(), "repo_states.json"),
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
{
|
||||||
},
|
[scannedRepos[0].repository.id]: {
|
||||||
[scannedRepos[1].repository.id]: {
|
repositoryId: scannedRepos[0].repository.id,
|
||||||
repositoryId: scannedRepos[1].repository.id,
|
downloadStatus:
|
||||||
downloadStatus:
|
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
[scannedRepos[1].repository.id]: {
|
||||||
|
repositoryId: scannedRepos[1].repository.id,
|
||||||
|
downloadStatus:
|
||||||
|
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update the repo state correctly", async () => {
|
||||||
|
mockRepoStates({
|
||||||
|
[scannedRepos[1].repository.id]: {
|
||||||
|
repositoryId: scannedRepos[1].repository.id,
|
||||||
|
downloadStatus:
|
||||||
|
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||||
|
},
|
||||||
|
[scannedRepos[2].repository.id]: {
|
||||||
|
repositoryId: scannedRepos[2].repository.id,
|
||||||
|
downloadStatus:
|
||||||
|
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have a failed repo state when the download fails", async () => {
|
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||||
getVariantAnalysisRepoResultStub.mockRejectedValueOnce(
|
|
||||||
new Error("Failed to download"),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(
|
expect(pathExistsStub).toBeCalledWith(
|
||||||
variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
join(storagePath, variantAnalysis.id.toString()),
|
||||||
scannedRepos[0],
|
);
|
||||||
variantAnalysis,
|
expect(readJsonStub).toHaveBeenCalledTimes(1);
|
||||||
cancellationTokenSource.token,
|
expect(readJsonStub).toHaveBeenCalledWith(
|
||||||
),
|
join(storagePath, variantAnalysis.id.toString(), "repo_states.json"),
|
||||||
).rejects.toThrow();
|
);
|
||||||
|
|
||||||
expect(outputJsonStub).not.toHaveBeenCalled();
|
pathExistsStub.mockRestore();
|
||||||
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
||||||
scannedRepos[1],
|
scannedRepos[0],
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
cancellationTokenSource.token,
|
cancellationTokenSource.token,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(outputJsonStub).toHaveBeenCalledWith(
|
expect(outputJsonStub).toHaveBeenCalledWith(
|
||||||
join(
|
join(storagePath, variantAnalysis.id.toString(), "repo_states.json"),
|
||||||
storagePath,
|
{
|
||||||
variantAnalysis.id.toString(),
|
|
||||||
"repo_states.json",
|
|
||||||
),
|
|
||||||
{
|
|
||||||
[scannedRepos[0].repository.id]: {
|
|
||||||
repositoryId: scannedRepos[0].repository.id,
|
|
||||||
downloadStatus:
|
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Failed,
|
|
||||||
},
|
|
||||||
[scannedRepos[1].repository.id]: {
|
|
||||||
repositoryId: scannedRepos[1].repository.id,
|
|
||||||
downloadStatus:
|
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should update the repo state correctly", async () => {
|
|
||||||
mockRepoStates({
|
|
||||||
[scannedRepos[1].repository.id]: {
|
[scannedRepos[1].repository.id]: {
|
||||||
repositoryId: scannedRepos[1].repository.id,
|
repositoryId: scannedRepos[1].repository.id,
|
||||||
downloadStatus:
|
downloadStatus:
|
||||||
|
@ -640,66 +646,22 @@ describe("Variant Analysis Manager", () => {
|
||||||
downloadStatus:
|
downloadStatus:
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
||||||
},
|
},
|
||||||
});
|
[scannedRepos[0].repository.id]: {
|
||||||
|
repositoryId: scannedRepos[0].repository.id,
|
||||||
await variantAnalysisManager.rehydrateVariantAnalysis(
|
downloadStatus:
|
||||||
variantAnalysis,
|
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||||
);
|
|
||||||
|
|
||||||
expect(pathExistsStub).toBeCalledWith(
|
|
||||||
join(storagePath, variantAnalysis.id.toString()),
|
|
||||||
);
|
|
||||||
expect(readJsonStub).toHaveBeenCalledTimes(1);
|
|
||||||
expect(readJsonStub).toHaveBeenCalledWith(
|
|
||||||
join(
|
|
||||||
storagePath,
|
|
||||||
variantAnalysis.id.toString(),
|
|
||||||
"repo_states.json",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
pathExistsStub.mockRestore();
|
|
||||||
|
|
||||||
await variantAnalysisManager.autoDownloadVariantAnalysisResult(
|
|
||||||
scannedRepos[0],
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(outputJsonStub).toHaveBeenCalledWith(
|
|
||||||
join(
|
|
||||||
storagePath,
|
|
||||||
variantAnalysis.id.toString(),
|
|
||||||
"repo_states.json",
|
|
||||||
),
|
|
||||||
{
|
|
||||||
[scannedRepos[1].repository.id]: {
|
|
||||||
repositoryId: scannedRepos[1].repository.id,
|
|
||||||
downloadStatus:
|
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
|
||||||
},
|
|
||||||
[scannedRepos[2].repository.id]: {
|
|
||||||
repositoryId: scannedRepos[2].repository.id,
|
|
||||||
downloadStatus:
|
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.InProgress,
|
|
||||||
},
|
|
||||||
[scannedRepos[0].repository.id]: {
|
|
||||||
repositoryId: scannedRepos[0].repository.id,
|
|
||||||
downloadStatus:
|
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
function mockRepoStates(
|
|
||||||
repoStates: Record<number, VariantAnalysisScannedRepositoryState>,
|
|
||||||
) {
|
|
||||||
pathExistsStub.mockImplementation(() => true);
|
|
||||||
// This will read in the correct repo states
|
|
||||||
readJsonStub.mockImplementation(() => Promise.resolve(repoStates));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mockRepoStates(
|
||||||
|
repoStates: Record<number, VariantAnalysisScannedRepositoryState>,
|
||||||
|
) {
|
||||||
|
pathExistsStub.mockImplementation(() => true);
|
||||||
|
// This will read in the correct repo states
|
||||||
|
readJsonStub.mockImplementation(() => Promise.resolve(repoStates));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -876,6 +838,8 @@ describe("Variant Analysis Manager", () => {
|
||||||
|
|
||||||
let variantAnalysisStorageLocation: string;
|
let variantAnalysisStorageLocation: string;
|
||||||
|
|
||||||
|
let mockCredentials: Credentials;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
variantAnalysis = createMockVariantAnalysis({});
|
variantAnalysis = createMockVariantAnalysis({});
|
||||||
|
|
||||||
|
@ -889,82 +853,54 @@ describe("Variant Analysis Manager", () => {
|
||||||
);
|
);
|
||||||
await createTimestampFile(variantAnalysisStorageLocation);
|
await createTimestampFile(variantAnalysisStorageLocation);
|
||||||
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
|
||||||
|
|
||||||
|
mockCredentials = {
|
||||||
|
getOctokit: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
request: jest.fn(),
|
||||||
|
}),
|
||||||
|
} as unknown as Credentials;
|
||||||
|
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the credentials are invalid", () => {
|
it("should return early if the variant analysis is not found", async () => {
|
||||||
beforeEach(async () => {
|
try {
|
||||||
jest
|
await variantAnalysisManager.cancelVariantAnalysis(
|
||||||
.spyOn(Credentials, "initialize")
|
variantAnalysis.id + 100,
|
||||||
.mockResolvedValue(undefined as unknown as Credentials);
|
);
|
||||||
});
|
} catch (error: any) {
|
||||||
|
expect(error.message).toBe(
|
||||||
it("should return early", async () => {
|
`No variant analysis with id: ${variantAnalysis.id + 100}`,
|
||||||
try {
|
);
|
||||||
await variantAnalysisManager.cancelVariantAnalysis(
|
}
|
||||||
variantAnalysis.id,
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
expect(error.message).toBe("Error authenticating with GitHub");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the credentials are valid", () => {
|
it("should return early if the variant analysis does not have an actions workflow run id", async () => {
|
||||||
let mockCredentials: Credentials;
|
await variantAnalysisManager.onVariantAnalysisUpdated({
|
||||||
|
...variantAnalysis,
|
||||||
beforeEach(async () => {
|
actionsWorkflowRunId: undefined,
|
||||||
mockCredentials = {
|
|
||||||
getOctokit: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
request: jest.fn(),
|
|
||||||
}),
|
|
||||||
} as unknown as Credentials;
|
|
||||||
jest
|
|
||||||
.spyOn(Credentials, "initialize")
|
|
||||||
.mockResolvedValue(mockCredentials);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return early if the variant analysis is not found", async () => {
|
try {
|
||||||
try {
|
|
||||||
await variantAnalysisManager.cancelVariantAnalysis(
|
|
||||||
variantAnalysis.id + 100,
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
expect(error.message).toBe(
|
|
||||||
`No variant analysis with id: ${variantAnalysis.id + 100}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return early if the variant analysis does not have an actions workflow run id", async () => {
|
|
||||||
await variantAnalysisManager.onVariantAnalysisUpdated({
|
|
||||||
...variantAnalysis,
|
|
||||||
actionsWorkflowRunId: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await variantAnalysisManager.cancelVariantAnalysis(
|
|
||||||
variantAnalysis.id,
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
expect(error.message).toBe(
|
|
||||||
`No workflow run id for variant analysis with id: ${variantAnalysis.id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return cancel if valid", async () => {
|
|
||||||
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id);
|
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id);
|
||||||
|
} catch (error: any) {
|
||||||
expect(mockCancelVariantAnalysis).toBeCalledWith(
|
expect(error.message).toBe(
|
||||||
mockCredentials,
|
`No workflow run id for variant analysis with id: ${variantAnalysis.id}`,
|
||||||
variantAnalysis,
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return cancel if valid", async () => {
|
||||||
|
await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id);
|
||||||
|
|
||||||
|
expect(mockCancelVariantAnalysis).toBeCalledWith(
|
||||||
|
mockCredentials,
|
||||||
|
variantAnalysis,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -61,10 +61,7 @@ describe("Variant Analysis Monitor", () => {
|
||||||
"GitHub.vscode-codeql",
|
"GitHub.vscode-codeql",
|
||||||
)!
|
)!
|
||||||
.activate();
|
.activate();
|
||||||
variantAnalysisMonitor = new VariantAnalysisMonitor(
|
variantAnalysisMonitor = new VariantAnalysisMonitor(shouldCancelMonitor);
|
||||||
extension.ctx,
|
|
||||||
shouldCancelMonitor,
|
|
||||||
);
|
|
||||||
variantAnalysisMonitor.onVariantAnalysisChange(onVariantAnalysisChangeSpy);
|
variantAnalysisMonitor.onVariantAnalysisChange(onVariantAnalysisChangeSpy);
|
||||||
|
|
||||||
variantAnalysisManager = extension.variantAnalysisManager;
|
variantAnalysisManager = extension.variantAnalysisManager;
|
||||||
|
@ -77,260 +74,237 @@ describe("Variant Analysis Monitor", () => {
|
||||||
.mockRejectedValue(new Error("Not mocked"));
|
.mockRejectedValue(new Error("Not mocked"));
|
||||||
|
|
||||||
limitNumberOfAttemptsToMonitor();
|
limitNumberOfAttemptsToMonitor();
|
||||||
|
|
||||||
|
const mockCredentials = {
|
||||||
|
getOctokit: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
request: jest.fn(),
|
||||||
|
}),
|
||||||
|
} as unknown as Credentials;
|
||||||
|
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when credentials are invalid", () => {
|
it("should return early if variant analysis is cancelled", async () => {
|
||||||
|
cancellationTokenSource.cancel();
|
||||||
|
|
||||||
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return early if variant analysis should be cancelled", async () => {
|
||||||
|
shouldCancelMonitor.mockResolvedValue(true);
|
||||||
|
|
||||||
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the variant analysis fails", () => {
|
||||||
|
let mockFailedApiResponse: VariantAnalysisApiResponse;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest
|
mockFailedApiResponse = createFailedMockApiResponse();
|
||||||
.spyOn(Credentials, "initialize")
|
mockGetVariantAnalysis.mockResolvedValue(mockFailedApiResponse);
|
||||||
.mockResolvedValue(undefined as unknown as Credentials);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return early if credentials are wrong", async () => {
|
it("should mark as failed and stop monitoring", async () => {
|
||||||
try {
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockGetVariantAnalysis).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(onVariantAnalysisChangeSpy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
status: VariantAnalysisStatus.Failed,
|
||||||
|
failureReason: processFailureReason(
|
||||||
|
mockFailedApiResponse.failure_reason as VariantAnalysisFailureReason,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the variant analysis is in progress", () => {
|
||||||
|
let mockApiResponse: VariantAnalysisApiResponse;
|
||||||
|
let scannedRepos: ApiVariantAnalysisScannedRepository[];
|
||||||
|
let succeededRepos: ApiVariantAnalysisScannedRepository[];
|
||||||
|
|
||||||
|
describe("when there are successfully scanned repos", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
scannedRepos = createMockScannedRepos([
|
||||||
|
"pending",
|
||||||
|
"pending",
|
||||||
|
"in_progress",
|
||||||
|
"in_progress",
|
||||||
|
"succeeded",
|
||||||
|
"succeeded",
|
||||||
|
"succeeded",
|
||||||
|
]);
|
||||||
|
mockApiResponse = createMockApiResponse("succeeded", scannedRepos);
|
||||||
|
mockGetVariantAnalysis.mockResolvedValue(mockApiResponse);
|
||||||
|
succeededRepos = scannedRepos.filter(
|
||||||
|
(r) => r.analysis_status === "succeeded",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger a download extension command for each repo", async () => {
|
||||||
|
const succeededRepos = scannedRepos.filter(
|
||||||
|
(r) => r.analysis_status === "succeeded",
|
||||||
|
);
|
||||||
|
const commandSpy = jest
|
||||||
|
.spyOn(commands, "executeCommand")
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
cancellationTokenSource.token,
|
cancellationTokenSource.token,
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
|
||||||
expect(error.message).toBe("Error authenticating with GitHub");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when credentials are valid", () => {
|
expect(commandSpy).toBeCalledTimes(succeededRepos.length);
|
||||||
beforeEach(async () => {
|
|
||||||
const mockCredentials = {
|
succeededRepos.forEach((succeededRepo, index) => {
|
||||||
getOctokit: () =>
|
expect(commandSpy).toHaveBeenNthCalledWith(
|
||||||
Promise.resolve({
|
index + 1,
|
||||||
request: jest.fn(),
|
"codeQL.autoDownloadVariantAnalysisResult",
|
||||||
}),
|
processScannedRepository(succeededRepo),
|
||||||
} as unknown as Credentials;
|
processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse),
|
||||||
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should download all available results", async () => {
|
||||||
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockGetDownloadResult).toBeCalledTimes(succeededRepos.length);
|
||||||
|
|
||||||
|
succeededRepos.forEach((succeededRepo, index) => {
|
||||||
|
expect(mockGetDownloadResult).toHaveBeenNthCalledWith(
|
||||||
|
index + 1,
|
||||||
|
processScannedRepository(succeededRepo),
|
||||||
|
processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse),
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return early if variant analysis is cancelled", async () => {
|
describe("when there are only in progress repos", () => {
|
||||||
cancellationTokenSource.cancel();
|
let scannedRepos: ApiVariantAnalysisScannedRepository[];
|
||||||
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return early if variant analysis should be cancelled", async () => {
|
|
||||||
shouldCancelMonitor.mockResolvedValue(true);
|
|
||||||
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the variant analysis fails", () => {
|
|
||||||
let mockFailedApiResponse: VariantAnalysisApiResponse;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockFailedApiResponse = createFailedMockApiResponse();
|
scannedRepos = createMockScannedRepos(["pending", "in_progress"]);
|
||||||
mockGetVariantAnalysis.mockResolvedValue(mockFailedApiResponse);
|
mockApiResponse = createMockApiResponse("in_progress", scannedRepos);
|
||||||
|
mockGetVariantAnalysis.mockResolvedValue(mockApiResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should mark as failed and stop monitoring", async () => {
|
it("should succeed and not download any repos via a command", async () => {
|
||||||
|
const commandSpy = jest
|
||||||
|
.spyOn(commands, "executeCommand")
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
cancellationTokenSource.token,
|
cancellationTokenSource.token,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockGetVariantAnalysis).toHaveBeenCalledTimes(1);
|
expect(commandSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
expect(onVariantAnalysisChangeSpy).toHaveBeenCalledWith(
|
it("should not try to download any repos", async () => {
|
||||||
expect.objectContaining({
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
status: VariantAnalysisStatus.Failed,
|
variantAnalysis,
|
||||||
failureReason: processFailureReason(
|
cancellationTokenSource.token,
|
||||||
mockFailedApiResponse.failure_reason as VariantAnalysisFailureReason,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(mockGetDownloadResult).not.toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the variant analysis is in progress", () => {
|
describe("when the responses change", () => {
|
||||||
let mockApiResponse: VariantAnalysisApiResponse;
|
|
||||||
let scannedRepos: ApiVariantAnalysisScannedRepository[];
|
let scannedRepos: ApiVariantAnalysisScannedRepository[];
|
||||||
let succeededRepos: ApiVariantAnalysisScannedRepository[];
|
|
||||||
|
|
||||||
describe("when there are successfully scanned repos", () => {
|
beforeEach(async () => {
|
||||||
beforeEach(async () => {
|
scannedRepos = createMockScannedRepos([
|
||||||
scannedRepos = createMockScannedRepos([
|
"pending",
|
||||||
"pending",
|
"in_progress",
|
||||||
"pending",
|
"in_progress",
|
||||||
"in_progress",
|
"in_progress",
|
||||||
"in_progress",
|
"pending",
|
||||||
"succeeded",
|
"pending",
|
||||||
"succeeded",
|
]);
|
||||||
"succeeded",
|
mockApiResponse = createMockApiResponse("in_progress", scannedRepos);
|
||||||
]);
|
mockGetVariantAnalysis.mockResolvedValueOnce(mockApiResponse);
|
||||||
mockApiResponse = createMockApiResponse("succeeded", scannedRepos);
|
|
||||||
mockGetVariantAnalysis.mockResolvedValue(mockApiResponse);
|
|
||||||
succeededRepos = scannedRepos.filter(
|
|
||||||
(r) => r.analysis_status === "succeeded",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should trigger a download extension command for each repo", async () => {
|
let nextApiResponse = {
|
||||||
const succeededRepos = scannedRepos.filter(
|
...mockApiResponse,
|
||||||
(r) => r.analysis_status === "succeeded",
|
scanned_repositories: [...scannedRepos.map((r) => ({ ...r }))],
|
||||||
);
|
};
|
||||||
const commandSpy = jest
|
nextApiResponse.scanned_repositories[0].analysis_status = "succeeded";
|
||||||
.spyOn(commands, "executeCommand")
|
nextApiResponse.scanned_repositories[1].analysis_status = "succeeded";
|
||||||
.mockResolvedValue(undefined);
|
mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse);
|
||||||
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
nextApiResponse = {
|
||||||
variantAnalysis,
|
...mockApiResponse,
|
||||||
cancellationTokenSource.token,
|
scanned_repositories: [
|
||||||
);
|
...nextApiResponse.scanned_repositories.map((r) => ({ ...r })),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
nextApiResponse.scanned_repositories[2].analysis_status = "succeeded";
|
||||||
|
nextApiResponse.scanned_repositories[5].analysis_status = "succeeded";
|
||||||
|
mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse);
|
||||||
|
|
||||||
expect(commandSpy).toBeCalledTimes(succeededRepos.length);
|
nextApiResponse = {
|
||||||
|
...mockApiResponse,
|
||||||
succeededRepos.forEach((succeededRepo, index) => {
|
scanned_repositories: [
|
||||||
expect(commandSpy).toHaveBeenNthCalledWith(
|
...nextApiResponse.scanned_repositories.map((r) => ({ ...r })),
|
||||||
index + 1,
|
],
|
||||||
"codeQL.autoDownloadVariantAnalysisResult",
|
};
|
||||||
processScannedRepository(succeededRepo),
|
nextApiResponse.scanned_repositories[3].analysis_status = "succeeded";
|
||||||
processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse),
|
nextApiResponse.scanned_repositories[4].analysis_status = "failed";
|
||||||
);
|
mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should download all available results", async () => {
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mockGetDownloadResult).toBeCalledTimes(succeededRepos.length);
|
|
||||||
|
|
||||||
succeededRepos.forEach((succeededRepo, index) => {
|
|
||||||
expect(mockGetDownloadResult).toHaveBeenNthCalledWith(
|
|
||||||
index + 1,
|
|
||||||
processScannedRepository(succeededRepo),
|
|
||||||
processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse),
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when there are only in progress repos", () => {
|
it("should trigger a download extension command for each repo", async () => {
|
||||||
let scannedRepos: ApiVariantAnalysisScannedRepository[];
|
const commandSpy = jest
|
||||||
|
.spyOn(commands, "executeCommand")
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
beforeEach(async () => {
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
scannedRepos = createMockScannedRepos(["pending", "in_progress"]);
|
variantAnalysis,
|
||||||
mockApiResponse = createMockApiResponse("in_progress", scannedRepos);
|
cancellationTokenSource.token,
|
||||||
mockGetVariantAnalysis.mockResolvedValue(mockApiResponse);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it("should succeed and not download any repos via a command", async () => {
|
expect(mockGetVariantAnalysis).toBeCalledTimes(4);
|
||||||
const commandSpy = jest
|
expect(commandSpy).toBeCalledTimes(5);
|
||||||
.spyOn(commands, "executeCommand")
|
});
|
||||||
.mockResolvedValue(undefined);
|
});
|
||||||
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
describe("when there are no repos to scan", () => {
|
||||||
variantAnalysis,
|
beforeEach(async () => {
|
||||||
cancellationTokenSource.token,
|
scannedRepos = [];
|
||||||
);
|
mockApiResponse = createMockApiResponse("succeeded", scannedRepos);
|
||||||
|
mockGetVariantAnalysis.mockResolvedValue(mockApiResponse);
|
||||||
expect(commandSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not try to download any repos", async () => {
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mockGetDownloadResult).not.toBeCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the responses change", () => {
|
it("should not try to download any repos", async () => {
|
||||||
let scannedRepos: ApiVariantAnalysisScannedRepository[];
|
await variantAnalysisMonitor.monitorVariantAnalysis(
|
||||||
|
variantAnalysis,
|
||||||
|
cancellationTokenSource.token,
|
||||||
|
);
|
||||||
|
|
||||||
beforeEach(async () => {
|
expect(mockGetDownloadResult).not.toBeCalled();
|
||||||
scannedRepos = createMockScannedRepos([
|
|
||||||
"pending",
|
|
||||||
"in_progress",
|
|
||||||
"in_progress",
|
|
||||||
"in_progress",
|
|
||||||
"pending",
|
|
||||||
"pending",
|
|
||||||
]);
|
|
||||||
mockApiResponse = createMockApiResponse("in_progress", scannedRepos);
|
|
||||||
mockGetVariantAnalysis.mockResolvedValueOnce(mockApiResponse);
|
|
||||||
|
|
||||||
let nextApiResponse = {
|
|
||||||
...mockApiResponse,
|
|
||||||
scanned_repositories: [...scannedRepos.map((r) => ({ ...r }))],
|
|
||||||
};
|
|
||||||
nextApiResponse.scanned_repositories[0].analysis_status = "succeeded";
|
|
||||||
nextApiResponse.scanned_repositories[1].analysis_status = "succeeded";
|
|
||||||
mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse);
|
|
||||||
|
|
||||||
nextApiResponse = {
|
|
||||||
...mockApiResponse,
|
|
||||||
scanned_repositories: [
|
|
||||||
...nextApiResponse.scanned_repositories.map((r) => ({ ...r })),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
nextApiResponse.scanned_repositories[2].analysis_status = "succeeded";
|
|
||||||
nextApiResponse.scanned_repositories[5].analysis_status = "succeeded";
|
|
||||||
mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse);
|
|
||||||
|
|
||||||
nextApiResponse = {
|
|
||||||
...mockApiResponse,
|
|
||||||
scanned_repositories: [
|
|
||||||
...nextApiResponse.scanned_repositories.map((r) => ({ ...r })),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
nextApiResponse.scanned_repositories[3].analysis_status = "succeeded";
|
|
||||||
nextApiResponse.scanned_repositories[4].analysis_status = "failed";
|
|
||||||
mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should trigger a download extension command for each repo", async () => {
|
|
||||||
const commandSpy = jest
|
|
||||||
.spyOn(commands, "executeCommand")
|
|
||||||
.mockResolvedValue(undefined);
|
|
||||||
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mockGetVariantAnalysis).toBeCalledTimes(4);
|
|
||||||
expect(commandSpy).toBeCalledTimes(5);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when there are no repos to scan", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
scannedRepos = [];
|
|
||||||
mockApiResponse = createMockApiResponse("succeeded", scannedRepos);
|
|
||||||
mockGetVariantAnalysis.mockResolvedValue(mockApiResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not try to download any repos", async () => {
|
|
||||||
await variantAnalysisMonitor.monitorVariantAnalysis(
|
|
||||||
variantAnalysis,
|
|
||||||
cancellationTokenSource.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mockGetDownloadResult).not.toBeCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { readFile } from "fs-extra";
|
import { readFile } from "fs-extra";
|
||||||
import { createMockExtensionContext } from "../index";
|
|
||||||
import { Credentials } from "../../../authentication";
|
import { Credentials } from "../../../authentication";
|
||||||
import * as markdownGenerator from "../../../remote-queries/remote-queries-markdown-generation";
|
import * as markdownGenerator from "../../../remote-queries/remote-queries-markdown-generation";
|
||||||
import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client";
|
import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client";
|
||||||
|
@ -20,7 +19,6 @@ describe("export results", () => {
|
||||||
.spyOn(ghApiClient, "createGist")
|
.spyOn(ghApiClient, "createGist")
|
||||||
.mockResolvedValue(undefined);
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
const ctx = createMockExtensionContext();
|
|
||||||
const query = JSON.parse(
|
const query = JSON.parse(
|
||||||
await readFile(
|
await readFile(
|
||||||
join(
|
join(
|
||||||
|
@ -41,7 +39,6 @@ describe("export results", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
await exportRemoteQueryAnalysisResults(
|
await exportRemoteQueryAnalysisResults(
|
||||||
ctx,
|
|
||||||
"",
|
"",
|
||||||
query,
|
query,
|
||||||
analysesResults,
|
analysesResults,
|
||||||
|
|
|
@ -279,7 +279,6 @@ describe("Remote queries and query history manager", () => {
|
||||||
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
|
jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials);
|
||||||
|
|
||||||
arm = new AnalysesResultsManager(
|
arm = new AnalysesResultsManager(
|
||||||
{} as ExtensionContext,
|
|
||||||
mockCliServer,
|
mockCliServer,
|
||||||
join(STORAGE_DIR, "queries"),
|
join(STORAGE_DIR, "queries"),
|
||||||
mockLogger,
|
mockLogger,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче