Add prompt for updating GitHub databases
This commit is contained in:
@@ -441,6 +441,19 @@
|
||||
"Never download a GitHub databases when a workspace is opened."
|
||||
],
|
||||
"description": "Ask to download a GitHub database when a workspace is opened."
|
||||
},
|
||||
"codeQL.githubDatabase.update": {
|
||||
"type": "string",
|
||||
"default": "ask",
|
||||
"enum": [
|
||||
"ask",
|
||||
"never"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Ask to download an updated GitHub database when a new version is available.",
|
||||
"Never download an updated GitHub database when a new version is available."
|
||||
],
|
||||
"description": "Ask to download an updated GitHub database when a new version is available."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -788,13 +788,23 @@ const GITHUB_DATABASE_DOWNLOAD = new Setting(
|
||||
const GitHubDatabaseDownloadValues = ["ask", "never"] as const;
|
||||
type GitHubDatabaseDownload = (typeof GitHubDatabaseDownloadValues)[number];
|
||||
|
||||
const GITHUB_DATABASE_UPDATE = new Setting("update", GITHUB_DATABASE_SETTING);
|
||||
|
||||
const GitHubDatabaseUpdateValues = ["ask", "never"] as const;
|
||||
type GitHubDatabaseUpdate = (typeof GitHubDatabaseUpdateValues)[number];
|
||||
|
||||
export interface GitHubDatabaseConfig {
|
||||
enable: boolean;
|
||||
download: GitHubDatabaseDownload;
|
||||
update: GitHubDatabaseUpdate;
|
||||
setDownload(
|
||||
value: GitHubDatabaseDownload,
|
||||
target?: ConfigurationTarget,
|
||||
): Promise<void>;
|
||||
setUpdate(
|
||||
value: GitHubDatabaseUpdate,
|
||||
target?: ConfigurationTarget,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export class GitHubDatabaseConfigListener
|
||||
@@ -817,10 +827,22 @@ export class GitHubDatabaseConfigListener
|
||||
return GitHubDatabaseDownloadValues.includes(value) ? value : "ask";
|
||||
}
|
||||
|
||||
public get update(): GitHubDatabaseUpdate {
|
||||
const value = GITHUB_DATABASE_UPDATE.getValue<GitHubDatabaseUpdate>();
|
||||
return GitHubDatabaseUpdateValues.includes(value) ? value : "ask";
|
||||
}
|
||||
|
||||
public async setDownload(
|
||||
value: GitHubDatabaseDownload,
|
||||
target: ConfigurationTarget = ConfigurationTarget.Workspace,
|
||||
): Promise<void> {
|
||||
await GITHUB_DATABASE_DOWNLOAD.updateValue(value, target);
|
||||
}
|
||||
|
||||
public async setUpdate(
|
||||
value: GitHubDatabaseUpdate,
|
||||
target: ConfigurationTarget = ConfigurationTarget.Workspace,
|
||||
): Promise<void> {
|
||||
await GITHUB_DATABASE_UPDATE.updateValue(value, target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export async function downloadDatabaseFromGitHub(
|
||||
*
|
||||
* @param languages The languages to join. These should be language identifiers, such as `csharp`.
|
||||
*/
|
||||
function joinLanguages(languages: string[]): string {
|
||||
export function joinLanguages(languages: string[]): string {
|
||||
const languageDisplayNames = languages
|
||||
.map((language) => getLanguageDisplayName(language))
|
||||
.sort();
|
||||
@@ -130,8 +130,17 @@ function joinLanguages(languages: string[]): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function promptForDatabases(
|
||||
type PromptForDatabasesOptions = {
|
||||
title?: string;
|
||||
placeHolder?: string;
|
||||
};
|
||||
|
||||
export async function promptForDatabases(
|
||||
databases: CodeqlDatabase[],
|
||||
{
|
||||
title = "Select databases to download",
|
||||
placeHolder = "Databases found in this repository",
|
||||
}: PromptForDatabasesOptions = {},
|
||||
): Promise<CodeqlDatabase[]> {
|
||||
if (databases.length === 1) {
|
||||
return databases;
|
||||
@@ -152,8 +161,8 @@ async function promptForDatabases(
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const selectedItems = await window.showQuickPick(items, {
|
||||
title: "Select databases to download",
|
||||
placeHolder: "Databases found in this repository",
|
||||
title,
|
||||
placeHolder,
|
||||
ignoreFocusOut: true,
|
||||
canPickMany: true,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DisposableObject } from "../common/disposable-object";
|
||||
import { App } from "../common/app";
|
||||
import { findGitHubRepositoryForWorkspace } from "./github-repository-finder";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { asError, getErrorMessage } from "../common/helpers-pure";
|
||||
import { asError, assertNever, getErrorMessage } from "../common/helpers-pure";
|
||||
import {
|
||||
askForGitHubDatabaseDownload,
|
||||
downloadDatabaseFromGitHub,
|
||||
@@ -12,6 +12,11 @@ import { GitHubDatabaseConfig, GitHubDatabaseConfigListener } from "../config";
|
||||
import { DatabaseManager } from "./local-databases";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { listDatabases, ListDatabasesResult } from "./github-database-api";
|
||||
import {
|
||||
askForGitHubDatabaseUpdate,
|
||||
downloadDatabaseUpdateFromGitHub,
|
||||
isNewerDatabaseAvailable,
|
||||
} from "./github-database-updates";
|
||||
|
||||
export class GithubDatabaseModule extends DisposableObject {
|
||||
private readonly config: GitHubDatabaseConfig;
|
||||
@@ -79,16 +84,6 @@ export class GithubDatabaseModule extends DisposableObject {
|
||||
|
||||
const githubRepository = githubRepositoryResult.value;
|
||||
|
||||
const hasExistingDatabase = this.databaseManager.databaseItems.some(
|
||||
(db) =>
|
||||
db.origin?.type === "github" &&
|
||||
db.origin.repository ===
|
||||
`${githubRepository.owner}/${githubRepository.name}`,
|
||||
);
|
||||
if (hasExistingDatabase) {
|
||||
return;
|
||||
}
|
||||
|
||||
let result: ListDatabasesResult | undefined;
|
||||
try {
|
||||
result = await listDatabases(
|
||||
@@ -130,6 +125,48 @@ export class GithubDatabaseModule extends DisposableObject {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateStatus = isNewerDatabaseAvailable(
|
||||
databases,
|
||||
githubRepository.owner,
|
||||
githubRepository.name,
|
||||
this.databaseManager,
|
||||
);
|
||||
if (updateStatus.type === "upToDate") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateStatus.type === "updateAvailable") {
|
||||
if (this.config.update === "never") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await askForGitHubDatabaseUpdate(
|
||||
updateStatus.databaseUpdates,
|
||||
this.config,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await downloadDatabaseUpdateFromGitHub(
|
||||
octokit,
|
||||
githubRepository.owner,
|
||||
githubRepository.name,
|
||||
updateStatus.databaseUpdates,
|
||||
this.databaseManager,
|
||||
this.databaseStoragePath,
|
||||
this.cliServer,
|
||||
this.app.commands,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateStatus.type !== "noDatabase") {
|
||||
assertNever(updateStatus);
|
||||
}
|
||||
|
||||
// If the user already had an access token, first ask if they even want to download the DB.
|
||||
if (!promptedForCredentials) {
|
||||
if (!(await askForGitHubDatabaseDownload(databases, this.config))) {
|
||||
|
||||
205
extensions/ql-vscode/src/databases/github-database-updates.ts
Normal file
205
extensions/ql-vscode/src/databases/github-database-updates.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { CodeqlDatabase } from "./github-database-api";
|
||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
import { getLanguageDisplayName } from "../common/query-language";
|
||||
import { showNeverAskAgainDialog } from "../common/vscode/dialog";
|
||||
import { downloadGitHubDatabaseFromUrl } from "./database-fetcher";
|
||||
import { withProgress } from "../common/vscode/progress";
|
||||
import { window } from "vscode";
|
||||
import { GitHubDatabaseConfig } from "../config";
|
||||
import { joinLanguages, promptForDatabases } from "./github-database-download";
|
||||
|
||||
export type DatabaseUpdate = {
|
||||
database: CodeqlDatabase;
|
||||
databaseItem: DatabaseItem;
|
||||
};
|
||||
|
||||
type DatabaseUpdateStatusUpdateAvailable = {
|
||||
type: "updateAvailable";
|
||||
databaseUpdates: DatabaseUpdate[];
|
||||
};
|
||||
|
||||
type DatabaseUpdateStatusUpToDate = {
|
||||
type: "upToDate";
|
||||
};
|
||||
|
||||
type DatabaseUpdateStatusNoDatabase = {
|
||||
type: "noDatabase";
|
||||
};
|
||||
|
||||
type DatabaseUpdateStatus =
|
||||
| DatabaseUpdateStatusUpdateAvailable
|
||||
| DatabaseUpdateStatusUpToDate
|
||||
| DatabaseUpdateStatusNoDatabase;
|
||||
|
||||
export function isNewerDatabaseAvailable(
|
||||
databases: CodeqlDatabase[],
|
||||
owner: string,
|
||||
name: string,
|
||||
databaseManager: DatabaseManager,
|
||||
): DatabaseUpdateStatus {
|
||||
// Sorted by date added ascending
|
||||
const existingDatabasesForRepository = databaseManager.databaseItems
|
||||
.filter(
|
||||
(db) =>
|
||||
db.origin?.type === "github" &&
|
||||
db.origin.repository === `${owner}/${name}`,
|
||||
)
|
||||
.sort((a, b) => (a.dateAdded ?? 0) - (b.dateAdded ?? 0));
|
||||
|
||||
if (existingDatabasesForRepository.length === 0) {
|
||||
return {
|
||||
type: "noDatabase",
|
||||
};
|
||||
}
|
||||
|
||||
// Sort order is guaranteed by the sort call above. The newest database is the last one.
|
||||
const newestExistingDatabasesByLanguage = new Map<string, DatabaseItem>();
|
||||
for (const existingDatabase of existingDatabasesForRepository) {
|
||||
newestExistingDatabasesByLanguage.set(
|
||||
existingDatabase.language,
|
||||
existingDatabase,
|
||||
);
|
||||
}
|
||||
|
||||
const databaseUpdates = Array.from(newestExistingDatabasesByLanguage.values())
|
||||
.map((newestExistingDatabase): DatabaseUpdate | null => {
|
||||
const origin = newestExistingDatabase.origin;
|
||||
if (origin?.type !== "github") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchingDatabase = databases.find(
|
||||
(db) => db.language === newestExistingDatabase.language,
|
||||
);
|
||||
if (!matchingDatabase) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (matchingDatabase.commit_oid === origin.commitOid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
database: matchingDatabase,
|
||||
databaseItem: newestExistingDatabase,
|
||||
};
|
||||
})
|
||||
.filter((update): update is DatabaseUpdate => update !== null)
|
||||
.sort((a, b) => a.database.language.localeCompare(b.database.language));
|
||||
|
||||
if (databaseUpdates.length === 0) {
|
||||
return {
|
||||
type: "upToDate",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "updateAvailable",
|
||||
databaseUpdates,
|
||||
};
|
||||
}
|
||||
|
||||
export async function askForGitHubDatabaseUpdate(
|
||||
updates: DatabaseUpdate[],
|
||||
config: GitHubDatabaseConfig,
|
||||
): Promise<boolean> {
|
||||
const languages = updates.map((update) => update.database.language);
|
||||
|
||||
const message =
|
||||
updates.length === 1
|
||||
? `There is a newer ${getLanguageDisplayName(
|
||||
languages[0],
|
||||
)} CodeQL database available for this repository. Download the database update from GitHub?`
|
||||
: `There are newer ${joinLanguages(
|
||||
languages,
|
||||
)} CodeQL databases available for this repository. Download the database updates from GitHub?`;
|
||||
|
||||
const answer = await showNeverAskAgainDialog(
|
||||
message,
|
||||
false,
|
||||
"Download",
|
||||
"Not now",
|
||||
"Never",
|
||||
);
|
||||
|
||||
if (answer === "Not now" || answer === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (answer === "Never") {
|
||||
await config.setUpdate("never");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function downloadDatabaseUpdateFromGitHub(
|
||||
octokit: Octokit,
|
||||
owner: string,
|
||||
repo: string,
|
||||
updates: DatabaseUpdate[],
|
||||
databaseManager: DatabaseManager,
|
||||
storagePath: string,
|
||||
cliServer: CodeQLCliServer,
|
||||
commandManager: AppCommandManager,
|
||||
): Promise<void> {
|
||||
const selectedDatabases = await promptForDatabases(
|
||||
updates.map((update) => update.database),
|
||||
{
|
||||
title: "Select databases to update",
|
||||
},
|
||||
);
|
||||
if (selectedDatabases.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
selectedDatabases.map((database) => {
|
||||
const update = updates.find((update) => update.database === database);
|
||||
if (!update) {
|
||||
return;
|
||||
}
|
||||
|
||||
return withProgress(
|
||||
async (progress) => {
|
||||
const newDatabase = await downloadGitHubDatabaseFromUrl(
|
||||
database.url,
|
||||
database.id,
|
||||
database.created_at,
|
||||
database.commit_oid ?? null,
|
||||
owner,
|
||||
repo,
|
||||
octokit,
|
||||
progress,
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
databaseManager.currentDatabaseItem === update.databaseItem,
|
||||
update.databaseItem.hasSourceArchiveInExplorer(),
|
||||
);
|
||||
if (newDatabase === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
await databaseManager.removeDatabaseItem(update.databaseItem);
|
||||
|
||||
await commandManager.execute("codeQLDatabases.focus");
|
||||
void window.showInformationMessage(
|
||||
`Updated ${getLanguageDisplayName(
|
||||
database.language,
|
||||
)} database from GitHub.`,
|
||||
);
|
||||
},
|
||||
{
|
||||
title: `Updating ${getLanguageDisplayName(
|
||||
database.language,
|
||||
)} database from GitHub`,
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { QuickPickItem, window } from "vscode";
|
||||
import {
|
||||
mockDatabaseItem,
|
||||
mockedObject,
|
||||
mockedQuickPickItem,
|
||||
} from "../../utils/mocking.helpers";
|
||||
import { CodeqlDatabase } from "../../../../src/databases/github-database-api";
|
||||
import { DatabaseManager } from "../../../../src/databases/local-databases";
|
||||
import { GitHubDatabaseConfig } from "../../../../src/config";
|
||||
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
||||
import { createMockCommandManager } from "../../../__mocks__/commandsMock";
|
||||
import * as databaseFetcher from "../../../../src/databases/database-fetcher";
|
||||
import * as dialog from "../../../../src/common/vscode/dialog";
|
||||
import {
|
||||
DatabaseUpdate,
|
||||
askForGitHubDatabaseUpdate,
|
||||
downloadDatabaseUpdateFromGitHub,
|
||||
isNewerDatabaseAvailable,
|
||||
} from "../../../../src/databases/github-database-updates";
|
||||
|
||||
describe("isNewerDatabaseAvailable", () => {
|
||||
const owner = "github";
|
||||
const repo = "codeql";
|
||||
let databases: CodeqlDatabase[];
|
||||
let databaseManager: DatabaseManager;
|
||||
|
||||
describe("when there are updates", () => {
|
||||
beforeEach(() => {
|
||||
databases = [
|
||||
mockedObject<CodeqlDatabase>({
|
||||
language: "java",
|
||||
commit_oid: "58e7476df3e464a0c9742b14cd4ca274b0993ebb",
|
||||
}),
|
||||
mockedObject<CodeqlDatabase>({
|
||||
language: "swift",
|
||||
commit_oid: "b81c25c0b73dd3c242068e8ab38bef25563a7c2d",
|
||||
}),
|
||||
mockedObject<CodeqlDatabase>({
|
||||
language: "javascript",
|
||||
commit_oid: "6e93915ff37ff8bcfc552d48f118895d60d0e7cd",
|
||||
}),
|
||||
mockedObject<CodeqlDatabase>({
|
||||
language: "ql",
|
||||
commit_oid: "9448fbfb88cdefe4298cc2e234a5a3c98958cae8",
|
||||
}),
|
||||
];
|
||||
|
||||
databaseManager = mockedObject<DatabaseManager>({
|
||||
databaseItems: [
|
||||
mockDatabaseItem({
|
||||
dateAdded: 1600477451789,
|
||||
language: "java",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/codeql",
|
||||
commitOid: "4487d1da9665231d1a076c60a78523f6275ad70f",
|
||||
},
|
||||
}),
|
||||
mockDatabaseItem({
|
||||
dateAdded: 50,
|
||||
language: "swift",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/codeql",
|
||||
commitOid: "2b020927d3c6eb407223a1baa3d6ce3597a3f88d",
|
||||
},
|
||||
}),
|
||||
mockDatabaseItem({
|
||||
dateAdded: 1700477451789,
|
||||
language: "java",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/codeql",
|
||||
commitOid: "17663af4e84a3a010fcb3f09cc06049797dfb22a",
|
||||
},
|
||||
}),
|
||||
mockDatabaseItem({
|
||||
dateAdded: faker.date.past().getTime(),
|
||||
language: "golang",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/codeql",
|
||||
},
|
||||
}),
|
||||
mockDatabaseItem({
|
||||
dateAdded: faker.date.past().getTime(),
|
||||
language: "ql",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/codeql",
|
||||
},
|
||||
}),
|
||||
mockDatabaseItem({
|
||||
language: "javascript",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/vscode-codeql",
|
||||
commitOid: "fb360f9c09ac8c5edb2f18be5de4e80ea4c430d0",
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the correct status", async () => {
|
||||
expect(
|
||||
isNewerDatabaseAvailable(databases, owner, repo, databaseManager),
|
||||
).toEqual({
|
||||
type: "updateAvailable",
|
||||
databaseUpdates: [
|
||||
{
|
||||
database: databases[0],
|
||||
databaseItem: databaseManager.databaseItems[2],
|
||||
},
|
||||
{
|
||||
database: databases[3],
|
||||
databaseItem: databaseManager.databaseItems[4],
|
||||
},
|
||||
{
|
||||
database: databases[1],
|
||||
databaseItem: databaseManager.databaseItems[1],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are no updates", () => {
|
||||
beforeEach(() => {
|
||||
databases = [
|
||||
mockedObject<CodeqlDatabase>({
|
||||
language: "java",
|
||||
commit_oid: "17663af4e84a3a010fcb3f09cc06049797dfb22a",
|
||||
}),
|
||||
];
|
||||
|
||||
databaseManager = mockedObject<DatabaseManager>({
|
||||
databaseItems: [
|
||||
mockDatabaseItem({
|
||||
dateAdded: 1700477451789,
|
||||
language: "java",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/codeql",
|
||||
commitOid: "17663af4e84a3a010fcb3f09cc06049797dfb22a",
|
||||
},
|
||||
}),
|
||||
mockDatabaseItem({
|
||||
dateAdded: 1600477451789,
|
||||
language: "java",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/codeql",
|
||||
commitOid: "4487d1da9665231d1a076c60a78523f6275ad70f",
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the correct status", async () => {
|
||||
expect(
|
||||
isNewerDatabaseAvailable(databases, owner, repo, databaseManager),
|
||||
).toEqual({
|
||||
type: "upToDate",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are no matching database items", () => {
|
||||
beforeEach(() => {
|
||||
databases = [
|
||||
mockedObject<CodeqlDatabase>({
|
||||
language: "java",
|
||||
commit_oid: "17663af4e84a3a010fcb3f09cc06049797dfb22a",
|
||||
}),
|
||||
];
|
||||
|
||||
databaseManager = mockedObject<DatabaseManager>({
|
||||
databaseItems: [
|
||||
mockDatabaseItem({
|
||||
language: "javascript",
|
||||
origin: {
|
||||
type: "github",
|
||||
repository: "github/vscode-codeql",
|
||||
commitOid: "fb360f9c09ac8c5edb2f18be5de4e80ea4c430d0",
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the correct status", async () => {
|
||||
expect(
|
||||
isNewerDatabaseAvailable(databases, owner, repo, databaseManager),
|
||||
).toEqual({
|
||||
type: "noDatabase",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("askForGitHubDatabaseUpdate", () => {
|
||||
const setUpdate = jest.fn();
|
||||
let config: GitHubDatabaseConfig;
|
||||
|
||||
const updates: DatabaseUpdate[] = [
|
||||
{
|
||||
database: mockedObject<CodeqlDatabase>({
|
||||
id: faker.number.int(),
|
||||
created_at: faker.date.past().toISOString(),
|
||||
commit_oid: faker.git.commitSha(),
|
||||
language: "swift",
|
||||
size: 27389673,
|
||||
url: faker.internet.url({
|
||||
protocol: "https",
|
||||
}),
|
||||
}),
|
||||
databaseItem: mockDatabaseItem(),
|
||||
},
|
||||
];
|
||||
|
||||
let showNeverAskAgainDialogSpy: jest.SpiedFunction<
|
||||
typeof dialog.showNeverAskAgainDialog
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
config = mockedObject<GitHubDatabaseConfig>({
|
||||
setUpdate,
|
||||
});
|
||||
|
||||
showNeverAskAgainDialogSpy = jest.spyOn(dialog, "showNeverAskAgainDialog");
|
||||
});
|
||||
|
||||
describe("when answering download to prompt", () => {
|
||||
beforeEach(() => {
|
||||
showNeverAskAgainDialogSpy.mockResolvedValue("Download");
|
||||
});
|
||||
|
||||
it("returns false", async () => {
|
||||
expect(await askForGitHubDatabaseUpdate(updates, config)).toEqual(true);
|
||||
|
||||
expect(setUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when answering not now to prompt", () => {
|
||||
beforeEach(() => {
|
||||
showNeverAskAgainDialogSpy.mockResolvedValue("Not now");
|
||||
});
|
||||
|
||||
it("returns false", async () => {
|
||||
expect(await askForGitHubDatabaseUpdate(updates, config)).toEqual(false);
|
||||
|
||||
expect(setUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when cancelling prompt", () => {
|
||||
beforeEach(() => {
|
||||
showNeverAskAgainDialogSpy.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("returns false", async () => {
|
||||
expect(await askForGitHubDatabaseUpdate(updates, config)).toEqual(false);
|
||||
|
||||
expect(setUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when answering never to prompt", () => {
|
||||
beforeEach(() => {
|
||||
showNeverAskAgainDialogSpy.mockResolvedValue("Never");
|
||||
});
|
||||
|
||||
it("returns false", async () => {
|
||||
expect(await askForGitHubDatabaseUpdate(updates, config)).toEqual(false);
|
||||
});
|
||||
|
||||
it("sets the config to never", async () => {
|
||||
await askForGitHubDatabaseUpdate(updates, config);
|
||||
|
||||
expect(setUpdate).toHaveBeenCalledWith("never");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("downloadDatabaseUpdateFromGitHub", () => {
|
||||
let octokit: Octokit;
|
||||
const owner = "github";
|
||||
const repo = "codeql";
|
||||
let databaseManager: DatabaseManager;
|
||||
const storagePath = "/a/b/c/d";
|
||||
let cliServer: CodeQLCliServer;
|
||||
const commandManager = createMockCommandManager();
|
||||
|
||||
let updates: DatabaseUpdate[] = [
|
||||
{
|
||||
database: mockedObject<CodeqlDatabase>({
|
||||
id: faker.number.int(),
|
||||
created_at: faker.date.past().toISOString(),
|
||||
commit_oid: faker.git.commitSha(),
|
||||
language: "swift",
|
||||
size: 27389673,
|
||||
url: faker.internet.url({
|
||||
protocol: "https",
|
||||
}),
|
||||
}),
|
||||
databaseItem: mockDatabaseItem({
|
||||
hasSourceArchiveInExplorer: () => false,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
let showQuickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
let downloadGitHubDatabaseFromUrlSpy: jest.SpiedFunction<
|
||||
typeof databaseFetcher.downloadGitHubDatabaseFromUrl
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
octokit = mockedObject<Octokit>({});
|
||||
databaseManager = mockedObject<DatabaseManager>({
|
||||
currentDatabaseItem: mockDatabaseItem(),
|
||||
});
|
||||
cliServer = mockedObject<CodeQLCliServer>({});
|
||||
|
||||
showQuickPickSpy = jest.spyOn(window, "showQuickPick").mockResolvedValue(
|
||||
mockedQuickPickItem([
|
||||
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
|
||||
database: updates[0].database,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
downloadGitHubDatabaseFromUrlSpy = jest
|
||||
.spyOn(databaseFetcher, "downloadGitHubDatabaseFromUrl")
|
||||
.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("downloads the database", async () => {
|
||||
await downloadDatabaseUpdateFromGitHub(
|
||||
octokit,
|
||||
owner,
|
||||
repo,
|
||||
updates,
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
commandManager,
|
||||
);
|
||||
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledTimes(1);
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
|
||||
updates[0].database.url,
|
||||
updates[0].database.id,
|
||||
updates[0].database.created_at,
|
||||
updates[0].database.commit_oid,
|
||||
owner,
|
||||
repo,
|
||||
octokit,
|
||||
expect.anything(),
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
expect(showQuickPickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when there are multiple languages", () => {
|
||||
beforeEach(() => {
|
||||
updates = [
|
||||
{
|
||||
database: mockedObject<CodeqlDatabase>({
|
||||
id: faker.number.int(),
|
||||
created_at: faker.date.past().toISOString(),
|
||||
commit_oid: faker.git.commitSha(),
|
||||
language: "swift",
|
||||
size: 27389673,
|
||||
url: faker.internet.url({
|
||||
protocol: "https",
|
||||
}),
|
||||
}),
|
||||
databaseItem: mockDatabaseItem({
|
||||
hasSourceArchiveInExplorer: () => false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
database: mockedObject<CodeqlDatabase>({
|
||||
id: faker.number.int(),
|
||||
created_at: faker.date.past().toISOString(),
|
||||
commit_oid: null,
|
||||
language: "go",
|
||||
size: 2930572385,
|
||||
url: faker.internet.url({
|
||||
protocol: "https",
|
||||
}),
|
||||
}),
|
||||
databaseItem: mockDatabaseItem({
|
||||
hasSourceArchiveInExplorer: () => true,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
databaseManager = mockedObject<DatabaseManager>({
|
||||
currentDatabaseItem: updates[1].databaseItem,
|
||||
});
|
||||
});
|
||||
|
||||
it("downloads a single selected language", async () => {
|
||||
showQuickPickSpy.mockResolvedValue(
|
||||
mockedQuickPickItem([
|
||||
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
|
||||
database: updates[1].database,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await downloadDatabaseUpdateFromGitHub(
|
||||
octokit,
|
||||
owner,
|
||||
repo,
|
||||
updates,
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
commandManager,
|
||||
);
|
||||
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledTimes(1);
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
|
||||
updates[1].database.url,
|
||||
updates[1].database.id,
|
||||
updates[1].database.created_at,
|
||||
updates[1].database.commit_oid,
|
||||
owner,
|
||||
repo,
|
||||
octokit,
|
||||
expect.anything(),
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
expect.objectContaining({
|
||||
label: "Go",
|
||||
description: "2794.8 MB",
|
||||
database: updates[1].database,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
label: "Swift",
|
||||
description: "26.1 MB",
|
||||
database: updates[0].database,
|
||||
}),
|
||||
],
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("downloads multiple selected languages", async () => {
|
||||
showQuickPickSpy.mockResolvedValue(
|
||||
mockedQuickPickItem([
|
||||
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
|
||||
database: updates[0].database,
|
||||
}),
|
||||
mockedObject<QuickPickItem & { database: CodeqlDatabase }>({
|
||||
database: updates[1].database,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await downloadDatabaseUpdateFromGitHub(
|
||||
octokit,
|
||||
owner,
|
||||
repo,
|
||||
updates,
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
commandManager,
|
||||
);
|
||||
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledTimes(2);
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
|
||||
updates[0].database.url,
|
||||
updates[0].database.id,
|
||||
updates[0].database.created_at,
|
||||
updates[0].database.commit_oid,
|
||||
owner,
|
||||
repo,
|
||||
octokit,
|
||||
expect.anything(),
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).toHaveBeenCalledWith(
|
||||
updates[1].database.url,
|
||||
updates[1].database.id,
|
||||
updates[1].database.created_at,
|
||||
updates[1].database.commit_oid,
|
||||
owner,
|
||||
repo,
|
||||
octokit,
|
||||
expect.anything(),
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
expect(showQuickPickSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
expect.objectContaining({
|
||||
label: "Go",
|
||||
description: "2794.8 MB",
|
||||
database: updates[1].database,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
label: "Swift",
|
||||
description: "26.1 MB",
|
||||
database: updates[0].database,
|
||||
}),
|
||||
],
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
describe("when not selecting language", () => {
|
||||
beforeEach(() => {
|
||||
showQuickPickSpy.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("does not download the database", async () => {
|
||||
await downloadDatabaseUpdateFromGitHub(
|
||||
octokit,
|
||||
owner,
|
||||
repo,
|
||||
updates,
|
||||
databaseManager,
|
||||
storagePath,
|
||||
cliServer,
|
||||
commandManager,
|
||||
);
|
||||
|
||||
expect(downloadGitHubDatabaseFromUrlSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user