Merge remote-tracking branch 'origin/main' into koesie10/unique-database-names
This commit is contained in:
@@ -176,3 +176,32 @@ export function findCommonParentDir(...paths: string[]): string {
|
||||
function isTopLevelPath(path: string): boolean {
|
||||
return dirname(path) === path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively looks for a file in a directory. If the file exists, then returns the directory containing the file.
|
||||
*
|
||||
* @param dir The directory to search
|
||||
* @param toFind The file to recursively look for in this directory
|
||||
*
|
||||
* @returns the directory containing the file, or undefined if not found.
|
||||
*/
|
||||
export async function findDirWithFile(
|
||||
dir: string,
|
||||
...toFind: string[]
|
||||
): Promise<string | undefined> {
|
||||
if (!(await stat(dir)).isDirectory()) {
|
||||
return;
|
||||
}
|
||||
const files = await readdir(dir);
|
||||
if (toFind.some((file) => files.includes(file))) {
|
||||
return dir;
|
||||
}
|
||||
for (const file of files) {
|
||||
const newPath = join(dir, file);
|
||||
const result = await findDirWithFile(newPath, ...toFind);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import type {
|
||||
CancellationToken,
|
||||
WebviewView,
|
||||
WebviewViewProvider,
|
||||
WebviewViewResolveContext,
|
||||
} from "vscode";
|
||||
import type { WebviewView, WebviewViewProvider } from "vscode";
|
||||
import { Uri } from "vscode";
|
||||
import type { WebviewKind, WebviewMessage } from "./webview-html";
|
||||
import { getHtmlForWebview } from "./webview-html";
|
||||
@@ -28,11 +23,7 @@ export abstract class AbstractWebviewViewProvider<
|
||||
* This is called when a view first becomes visible. This may happen when the view is
|
||||
* first loaded or when the user hides and then shows a view again.
|
||||
*/
|
||||
public resolveWebviewView(
|
||||
webviewView: WebviewView,
|
||||
_context: WebviewViewResolveContext,
|
||||
_token: CancellationToken,
|
||||
) {
|
||||
public resolveWebviewView(webviewView: WebviewView) {
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [Uri.file(this.app.extensionPath)],
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
pathExists,
|
||||
createWriteStream,
|
||||
remove,
|
||||
stat,
|
||||
readdir,
|
||||
} from "fs-extra";
|
||||
import { basename, join } from "path";
|
||||
@@ -36,11 +35,12 @@ import {
|
||||
} from "../config";
|
||||
import { showAndLogInformationMessage } from "../common/logging";
|
||||
import { AppOctokit } from "../common/octokit";
|
||||
import { getLanguageDisplayName } from "../common/query-language";
|
||||
import type { DatabaseOrigin } from "./local-databases/database-origin";
|
||||
import { createTimeoutSignal } from "../common/fetch-stream";
|
||||
import type { App } from "../common/app";
|
||||
import { createFilenameFromString } from "../common/filenames";
|
||||
import { findDirWithFile } from "../common/files";
|
||||
import { convertGithubNwoToDatabaseUrl } from "./github-databases/api";
|
||||
|
||||
/**
|
||||
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
|
||||
@@ -406,6 +406,7 @@ async function databaseArchiveFetcher(
|
||||
nameOverride,
|
||||
{
|
||||
addSourceArchiveFolder,
|
||||
extensionManagedLocation: unzipPath,
|
||||
},
|
||||
);
|
||||
return item;
|
||||
@@ -617,125 +618,6 @@ function isFile(databaseUrl: string) {
|
||||
return Uri.parse(databaseUrl).scheme === "file";
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively looks for a file in a directory. If the file exists, then returns the directory containing the file.
|
||||
*
|
||||
* @param dir The directory to search
|
||||
* @param toFind The file to recursively look for in this directory
|
||||
*
|
||||
* @returns the directory containing the file, or undefined if not found.
|
||||
*/
|
||||
// exported for testing
|
||||
export async function findDirWithFile(
|
||||
dir: string,
|
||||
...toFind: string[]
|
||||
): Promise<string | undefined> {
|
||||
if (!(await stat(dir)).isDirectory()) {
|
||||
return;
|
||||
}
|
||||
const files = await readdir(dir);
|
||||
if (toFind.some((file) => files.includes(file))) {
|
||||
return dir;
|
||||
}
|
||||
for (const file of files) {
|
||||
const newPath = join(dir, file);
|
||||
const result = await findDirWithFile(newPath, ...toFind);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export async function convertGithubNwoToDatabaseUrl(
|
||||
nwo: string,
|
||||
octokit: Octokit,
|
||||
progress: ProgressCallback,
|
||||
language?: string,
|
||||
): Promise<
|
||||
| {
|
||||
databaseUrl: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
databaseId: number;
|
||||
databaseCreatedAt: string;
|
||||
commitOid: string | null;
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
try {
|
||||
const [owner, repo] = nwo.split("/");
|
||||
|
||||
const response = await octokit.rest.codeScanning.listCodeqlDatabases({
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
|
||||
const languages = response.data.map((db) => db.language);
|
||||
|
||||
if (!language || !languages.includes(language)) {
|
||||
language = await promptForLanguage(languages, progress);
|
||||
if (!language) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const databaseForLanguage = response.data.find(
|
||||
(db) => db.language === language,
|
||||
);
|
||||
if (!databaseForLanguage) {
|
||||
throw new Error(`No database found for language '${language}'`);
|
||||
}
|
||||
|
||||
return {
|
||||
databaseUrl: databaseForLanguage.url,
|
||||
owner,
|
||||
name: repo,
|
||||
databaseId: databaseForLanguage.id,
|
||||
databaseCreatedAt: databaseForLanguage.created_at,
|
||||
commitOid: databaseForLanguage.commit_oid ?? null,
|
||||
};
|
||||
} catch (e) {
|
||||
void extLogger.log(`Error: ${getErrorMessage(e)}`);
|
||||
throw new Error(`Unable to get database for '${nwo}'`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function promptForLanguage(
|
||||
languages: string[],
|
||||
progress: ProgressCallback | undefined,
|
||||
): Promise<string | undefined> {
|
||||
progress?.({
|
||||
message: "Choose language",
|
||||
step: 2,
|
||||
maxStep: 2,
|
||||
});
|
||||
if (!languages.length) {
|
||||
throw new Error("No databases found");
|
||||
}
|
||||
if (languages.length === 1) {
|
||||
return languages[0];
|
||||
}
|
||||
|
||||
const items = languages
|
||||
.map((language) => ({
|
||||
label: getLanguageDisplayName(language),
|
||||
description: language,
|
||||
language,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const selectedItem = await window.showQuickPick(items, {
|
||||
placeHolder: "Select the database language to download:",
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (!selectedItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return selectedItem.language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Databases created by the old odasa tool will not have a zipped
|
||||
* source location. However, this extension works better if sources
|
||||
|
||||
@@ -5,6 +5,11 @@ import { showNeverAskAgainDialog } from "../../common/vscode/dialog";
|
||||
import type { GitHubDatabaseConfig } from "../../config";
|
||||
import type { Credentials } from "../../common/authentication";
|
||||
import { AppOctokit } from "../../common/octokit";
|
||||
import type { ProgressCallback } from "../../common/vscode/progress";
|
||||
import { getErrorMessage } from "../../common/helpers-pure";
|
||||
import { getLanguageDisplayName } from "../../common/query-language";
|
||||
import { window } from "vscode";
|
||||
import { extLogger } from "../../common/logging/vscode";
|
||||
|
||||
export type CodeqlDatabase =
|
||||
RestEndpointMethodTypes["codeScanning"]["listCodeqlDatabases"]["response"]["data"][number];
|
||||
@@ -108,3 +113,92 @@ export async function listDatabases(
|
||||
octokit,
|
||||
};
|
||||
}
|
||||
|
||||
export async function convertGithubNwoToDatabaseUrl(
|
||||
nwo: string,
|
||||
octokit: Octokit,
|
||||
progress: ProgressCallback,
|
||||
language?: string,
|
||||
): Promise<
|
||||
| {
|
||||
databaseUrl: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
databaseId: number;
|
||||
databaseCreatedAt: string;
|
||||
commitOid: string | null;
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
try {
|
||||
const [owner, repo] = nwo.split("/");
|
||||
|
||||
const response = await octokit.rest.codeScanning.listCodeqlDatabases({
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
|
||||
const languages = response.data.map((db) => db.language);
|
||||
|
||||
if (!language || !languages.includes(language)) {
|
||||
language = await promptForLanguage(languages, progress);
|
||||
if (!language) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const databaseForLanguage = response.data.find(
|
||||
(db) => db.language === language,
|
||||
);
|
||||
if (!databaseForLanguage) {
|
||||
throw new Error(`No database found for language '${language}'`);
|
||||
}
|
||||
|
||||
return {
|
||||
databaseUrl: databaseForLanguage.url,
|
||||
owner,
|
||||
name: repo,
|
||||
databaseId: databaseForLanguage.id,
|
||||
databaseCreatedAt: databaseForLanguage.created_at,
|
||||
commitOid: databaseForLanguage.commit_oid ?? null,
|
||||
};
|
||||
} catch (e) {
|
||||
void extLogger.log(`Error: ${getErrorMessage(e)}`);
|
||||
throw new Error(`Unable to get database for '${nwo}'`);
|
||||
}
|
||||
}
|
||||
|
||||
async function promptForLanguage(
|
||||
languages: string[],
|
||||
progress: ProgressCallback | undefined,
|
||||
): Promise<string | undefined> {
|
||||
progress?.({
|
||||
message: "Choose language",
|
||||
step: 2,
|
||||
maxStep: 2,
|
||||
});
|
||||
if (!languages.length) {
|
||||
throw new Error("No databases found");
|
||||
}
|
||||
if (languages.length === 1) {
|
||||
return languages[0];
|
||||
}
|
||||
|
||||
const items = languages
|
||||
.map((language) => ({
|
||||
label: getLanguageDisplayName(language),
|
||||
description: language,
|
||||
language,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const selectedItem = await window.showQuickPick(items, {
|
||||
placeHolder: "Select the database language to download:",
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (!selectedItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return selectedItem.language;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ export class DatabaseItemImpl implements DatabaseItem {
|
||||
return this.options.origin;
|
||||
}
|
||||
|
||||
public get extensionManagedLocation(): string | undefined {
|
||||
return this.options.extensionManagedLocation;
|
||||
}
|
||||
|
||||
public resolveSourceFile(uriStr: string | undefined): Uri {
|
||||
const sourceArchive = this.sourceArchive;
|
||||
const uri = uriStr ? Uri.parse(uriStr, true) : undefined;
|
||||
|
||||
@@ -31,6 +31,12 @@ export interface DatabaseItem {
|
||||
*/
|
||||
readonly origin: DatabaseOrigin | undefined;
|
||||
|
||||
/**
|
||||
* The location of the base storage location as managed by the extension, or undefined
|
||||
* if unknown or not managed by the extension.
|
||||
*/
|
||||
readonly extensionManagedLocation: string | undefined;
|
||||
|
||||
/** If the database is invalid, describes why. */
|
||||
readonly error: Error | undefined;
|
||||
|
||||
|
||||
@@ -82,6 +82,10 @@ function eventFired<T>(
|
||||
}
|
||||
|
||||
type OpenDatabaseOptions = {
|
||||
/**
|
||||
* A location that is managed by the extension.
|
||||
*/
|
||||
extensionManagedLocation?: string;
|
||||
isTutorialDatabase?: boolean;
|
||||
/**
|
||||
* Whether to add a workspace folder containing the source archive to the workspace. Default is true.
|
||||
@@ -141,6 +145,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
makeSelected = true,
|
||||
displayName?: string,
|
||||
{
|
||||
extensionManagedLocation,
|
||||
isTutorialDatabase = false,
|
||||
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
|
||||
}: OpenDatabaseOptions = {},
|
||||
@@ -149,6 +154,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
uri,
|
||||
origin,
|
||||
displayName,
|
||||
extensionManagedLocation,
|
||||
);
|
||||
|
||||
return await this.addExistingDatabaseItem(
|
||||
@@ -202,6 +208,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
uri: vscode.Uri,
|
||||
origin: DatabaseOrigin | undefined,
|
||||
displayName: string | undefined,
|
||||
extensionManagedLocation?: string,
|
||||
): Promise<DatabaseItemImpl> {
|
||||
const contents = await DatabaseResolver.resolveDatabaseContents(uri);
|
||||
const fullOptions: FullDatabaseOptions = {
|
||||
@@ -210,6 +217,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
dateAdded: Date.now(),
|
||||
language: await this.getPrimaryLanguage(uri.fsPath),
|
||||
origin,
|
||||
extensionManagedLocation,
|
||||
};
|
||||
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions);
|
||||
|
||||
@@ -370,6 +378,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
let dateAdded = undefined;
|
||||
let language = undefined;
|
||||
let origin = undefined;
|
||||
let extensionManagedLocation = undefined;
|
||||
if (state.options) {
|
||||
if (typeof state.options.displayName === "string") {
|
||||
displayName = state.options.displayName;
|
||||
@@ -379,6 +388,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
}
|
||||
language = state.options.language;
|
||||
origin = state.options.origin;
|
||||
extensionManagedLocation = state.options.extensionManagedLocation;
|
||||
}
|
||||
|
||||
const dbBaseUri = vscode.Uri.parse(state.uri, true);
|
||||
@@ -392,6 +402,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
dateAdded,
|
||||
language,
|
||||
origin,
|
||||
extensionManagedLocation,
|
||||
};
|
||||
const item = new DatabaseItemImpl(dbBaseUri, undefined, fullOptions);
|
||||
|
||||
@@ -583,15 +594,20 @@ export class DatabaseManager extends DisposableObject {
|
||||
// Remove this database item from the allow-list
|
||||
await this.deregisterDatabase(item);
|
||||
|
||||
// Find whether we know directly which directory we should remove
|
||||
const directoryToRemove = item.extensionManagedLocation
|
||||
? vscode.Uri.file(item.extensionManagedLocation)
|
||||
: item.databaseUri;
|
||||
|
||||
// Delete folder from file system only if it is controlled by the extension
|
||||
if (this.isExtensionControlledLocation(item.databaseUri)) {
|
||||
if (this.isExtensionControlledLocation(directoryToRemove)) {
|
||||
void extLogger.log("Deleting database from filesystem.");
|
||||
await remove(item.databaseUri.fsPath).then(
|
||||
() => void extLogger.log(`Deleted '${item.databaseUri.fsPath}'`),
|
||||
await remove(directoryToRemove.fsPath).then(
|
||||
() => void extLogger.log(`Deleted '${directoryToRemove.fsPath}'`),
|
||||
(e: unknown) =>
|
||||
void extLogger.log(
|
||||
`Failed to delete '${
|
||||
item.databaseUri.fsPath
|
||||
directoryToRemove.fsPath
|
||||
}'. Reason: ${getErrorMessage(e)}`,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -5,10 +5,12 @@ export interface DatabaseOptions {
|
||||
dateAdded?: number | undefined;
|
||||
language?: string;
|
||||
origin?: DatabaseOrigin;
|
||||
extensionManagedLocation?: string;
|
||||
}
|
||||
|
||||
export interface FullDatabaseOptions extends DatabaseOptions {
|
||||
dateAdded: number | undefined;
|
||||
language: string | undefined;
|
||||
origin: DatabaseOrigin | undefined;
|
||||
extensionManagedLocation: string | undefined;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ interface InternalDbModelingState {
|
||||
modelEvaluationRun: ModelEvaluationRun | undefined;
|
||||
}
|
||||
|
||||
interface DbModelingState {
|
||||
export interface DbModelingState {
|
||||
readonly databaseItem: DatabaseItem;
|
||||
readonly methods: readonly Method[];
|
||||
readonly hideModeledMethods: boolean;
|
||||
@@ -36,7 +36,7 @@ interface DbModelingState {
|
||||
readonly modelEvaluationRun: ModelEvaluationRun | undefined;
|
||||
}
|
||||
|
||||
interface SelectedMethodDetails {
|
||||
export interface SelectedMethodDetails {
|
||||
readonly databaseItem: DatabaseItem;
|
||||
readonly method: Method;
|
||||
readonly usage: Usage | undefined;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ModelingEvents } from "../../../src/model-editor/modeling-events";
|
||||
|
||||
export function createMockModelingEvents({
|
||||
onActiveDbChanged = jest.fn(),
|
||||
onDbOpened = jest.fn(),
|
||||
onDbClosed = jest.fn(),
|
||||
onSelectedMethodChanged = jest.fn(),
|
||||
onMethodsChanged = jest.fn(),
|
||||
@@ -16,6 +17,7 @@ export function createMockModelingEvents({
|
||||
onModelEvaluationRunChanged = jest.fn(),
|
||||
}: {
|
||||
onActiveDbChanged?: ModelingEvents["onActiveDbChanged"];
|
||||
onDbOpened?: ModelingEvents["onDbOpened"];
|
||||
onDbClosed?: ModelingEvents["onDbClosed"];
|
||||
onSelectedMethodChanged?: ModelingEvents["onSelectedMethodChanged"];
|
||||
onMethodsChanged?: ModelingEvents["onMethodsChanged"];
|
||||
@@ -30,6 +32,7 @@ export function createMockModelingEvents({
|
||||
} = {}): ModelingEvents {
|
||||
return mockedObject<ModelingEvents>({
|
||||
onActiveDbChanged,
|
||||
onDbOpened,
|
||||
onDbClosed,
|
||||
onSelectedMethodChanged,
|
||||
onMethodsChanged,
|
||||
|
||||
@@ -3,17 +3,20 @@ import type { ModelingStore } from "../../../src/model-editor/modeling-store";
|
||||
|
||||
export function createMockModelingStore({
|
||||
initializeStateForDb = jest.fn(),
|
||||
getStateForActiveDb = jest.fn(),
|
||||
getStateForActiveDb = jest.fn().mockReturnValue(undefined),
|
||||
getSelectedMethodDetails = jest.fn().mockReturnValue(undefined),
|
||||
getModelEvaluationRun = jest.fn(),
|
||||
updateModelEvaluationRun = jest.fn(),
|
||||
}: {
|
||||
initializeStateForDb?: ModelingStore["initializeStateForDb"];
|
||||
getStateForActiveDb?: ModelingStore["getStateForActiveDb"];
|
||||
getSelectedMethodDetails?: ModelingStore["getSelectedMethodDetails"];
|
||||
getModelEvaluationRun?: ModelingStore["getModelEvaluationRun"];
|
||||
updateModelEvaluationRun?: ModelingStore["updateModelEvaluationRun"];
|
||||
} = {}): ModelingStore {
|
||||
return mockedObject<ModelingStore>({
|
||||
initializeStateForDb,
|
||||
getSelectedMethodDetails,
|
||||
getStateForActiveDb,
|
||||
getModelEvaluationRun,
|
||||
updateModelEvaluationRun,
|
||||
|
||||
@@ -14,11 +14,12 @@ export function mockDbOptions(): FullDatabaseOptions {
|
||||
origin: {
|
||||
type: "folder",
|
||||
},
|
||||
extensionManagedLocation: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockDB(
|
||||
dir: DirResult,
|
||||
dir: DirResult | string,
|
||||
dbOptions = mockDbOptions(),
|
||||
// source archive location must be a real(-ish) location since
|
||||
// tests will add this to the workspace location
|
||||
@@ -38,10 +39,18 @@ export function createMockDB(
|
||||
);
|
||||
}
|
||||
|
||||
export function sourceLocationUri(dir: DirResult) {
|
||||
export function sourceLocationUri(dir: DirResult | string) {
|
||||
if (typeof dir === "string") {
|
||||
return Uri.file(join(dir, "src.zip"));
|
||||
}
|
||||
|
||||
return Uri.file(join(dir.name, "src.zip"));
|
||||
}
|
||||
|
||||
export function dbLocationUri(dir: DirResult) {
|
||||
export function dbLocationUri(dir: DirResult | string) {
|
||||
if (typeof dir === "string") {
|
||||
return Uri.file(join(dir, "db"));
|
||||
}
|
||||
|
||||
return Uri.file(join(dir.name, "db"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { join, parse } from "path";
|
||||
import {
|
||||
containsPath,
|
||||
findCommonParentDir,
|
||||
findDirWithFile,
|
||||
gatherQlFiles,
|
||||
getDirectoryNamesInsidePath,
|
||||
pathsEqual,
|
||||
@@ -11,7 +12,13 @@ import {
|
||||
} from "../../../src/common/files";
|
||||
import type { DirResult } from "tmp";
|
||||
import { dirSync } from "tmp";
|
||||
import { ensureDirSync, symlinkSync, writeFileSync } from "fs-extra";
|
||||
import {
|
||||
createFileSync,
|
||||
ensureDirSync,
|
||||
mkdirSync,
|
||||
symlinkSync,
|
||||
writeFileSync,
|
||||
} from "fs-extra";
|
||||
import "../../matchers/toEqualPath";
|
||||
|
||||
describe("files", () => {
|
||||
@@ -592,3 +599,52 @@ describe("findCommonParentDir", () => {
|
||||
expect(commonDir).toEqualPath(dataDir);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findDirWithFile", () => {
|
||||
let dir: DirResult;
|
||||
beforeEach(() => {
|
||||
dir = dirSync({ unsafeCleanup: true });
|
||||
createFile("a");
|
||||
createFile("b");
|
||||
createFile("c");
|
||||
|
||||
createDir("dir1");
|
||||
createFile("dir1", "d");
|
||||
createFile("dir1", "e");
|
||||
createFile("dir1", "f");
|
||||
|
||||
createDir("dir2");
|
||||
createFile("dir2", "g");
|
||||
createFile("dir2", "h");
|
||||
createFile("dir2", "i");
|
||||
|
||||
createDir("dir2", "dir3");
|
||||
createFile("dir2", "dir3", "j");
|
||||
createFile("dir2", "dir3", "k");
|
||||
createFile("dir2", "dir3", "l");
|
||||
});
|
||||
|
||||
it("should find files", async () => {
|
||||
expect(await findDirWithFile(dir.name, "k")).toBe(
|
||||
join(dir.name, "dir2", "dir3"),
|
||||
);
|
||||
expect(await findDirWithFile(dir.name, "h")).toBe(join(dir.name, "dir2"));
|
||||
expect(await findDirWithFile(dir.name, "z", "a")).toBe(dir.name);
|
||||
// there's some slight indeterminism when more than one name exists
|
||||
// but in general, this will find files in the current directory before
|
||||
// finding files in sub-dirs
|
||||
expect(await findDirWithFile(dir.name, "k", "a")).toBe(dir.name);
|
||||
});
|
||||
|
||||
it("should not find files", async () => {
|
||||
expect(await findDirWithFile(dir.name, "x", "y", "z")).toBeUndefined();
|
||||
});
|
||||
|
||||
function createFile(...segments: string[]) {
|
||||
createFileSync(join(dir.name, ...segments));
|
||||
}
|
||||
|
||||
function createDir(...segments: string[]) {
|
||||
mkdirSync(join(dir.name, ...segments));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -242,6 +242,37 @@ describe("local databases", () => {
|
||||
await expect(pathExists(mockDbItem.databaseUri.fsPath)).resolves.toBe(
|
||||
false,
|
||||
);
|
||||
await expect(pathExists(dir.name)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("should remove a database item with an extension managed location", async () => {
|
||||
const dbLocation = join(dir.name, "org-repo-12");
|
||||
await ensureDir(dbLocation);
|
||||
|
||||
const mockDbItem = createMockDB(dbLocation, {
|
||||
...mockDbOptions(),
|
||||
extensionManagedLocation: dbLocation,
|
||||
});
|
||||
await ensureDir(mockDbItem.databaseUri.fsPath);
|
||||
|
||||
// pretend that this item is the first workspace folder in the list
|
||||
jest
|
||||
.spyOn(mockDbItem, "belongsToSourceArchiveExplorerUri")
|
||||
.mockReturnValue(true);
|
||||
|
||||
await (databaseManager as any).addDatabaseItem(mockDbItem);
|
||||
|
||||
updateSpy.mockClear();
|
||||
|
||||
await databaseManager.removeDatabaseItem(mockDbItem);
|
||||
|
||||
expect(databaseManager.databaseItems).toEqual([]);
|
||||
expect(updateSpy).toHaveBeenCalledWith("databaseList", []);
|
||||
// should remove the folder
|
||||
expect(workspace.updateWorkspaceFolders).toHaveBeenCalledWith(0, 1);
|
||||
|
||||
// should delete the complete extension managed location
|
||||
await expect(pathExists(dbLocation)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("should remove a database item outside of the extension controlled area", async () => {
|
||||
@@ -604,6 +635,7 @@ describe("local databases", () => {
|
||||
origin: {
|
||||
type: "folder",
|
||||
},
|
||||
extensionManagedLocation: undefined,
|
||||
};
|
||||
mockDbItem = createMockDB(dir, options);
|
||||
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
import { join } from "path";
|
||||
import { createFileSync, mkdirSync } from "fs-extra";
|
||||
import type { DirResult } from "tmp";
|
||||
import { dirSync } from "tmp";
|
||||
import { window } from "vscode";
|
||||
|
||||
import {
|
||||
convertGithubNwoToDatabaseUrl,
|
||||
findDirWithFile,
|
||||
} from "../../../../src/databases/database-fetcher";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
import {
|
||||
mockedObject,
|
||||
mockedOctokitFunction,
|
||||
mockedQuickPickItem,
|
||||
} from "../../utils/mocking.helpers";
|
||||
|
||||
// These tests make API calls and may need extra time to complete.
|
||||
jest.setTimeout(10000);
|
||||
|
||||
describe("database-fetcher", () => {
|
||||
describe("convertGithubNwoToDatabaseUrl", () => {
|
||||
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
|
||||
const progressSpy = jest.fn();
|
||||
const mockListCodeqlDatabases = mockedOctokitFunction<
|
||||
"codeScanning",
|
||||
"listCodeqlDatabases"
|
||||
>();
|
||||
const octokit = mockedObject<Octokit>({
|
||||
rest: {
|
||||
codeScanning: {
|
||||
listCodeqlDatabases: mockListCodeqlDatabases,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// We can't make the real octokit request (since we need credentials), so we mock the response.
|
||||
const successfullMockApiResponse = {
|
||||
data: [
|
||||
{
|
||||
id: 1495869,
|
||||
name: "csharp-database",
|
||||
language: "csharp",
|
||||
uploader: {},
|
||||
content_type: "application/zip",
|
||||
state: "uploaded",
|
||||
size: 55599715,
|
||||
created_at: "2022-03-24T10:46:24Z",
|
||||
updated_at: "2022-03-24T10:46:27Z",
|
||||
url: "https://api.github.com/repositories/143040428/code-scanning/codeql/databases/csharp",
|
||||
},
|
||||
{
|
||||
id: 1100671,
|
||||
name: "database.zip",
|
||||
language: "javascript",
|
||||
uploader: {},
|
||||
content_type: "application/zip",
|
||||
state: "uploaded",
|
||||
size: 29294434,
|
||||
created_at: "2022-03-01T16:00:04Z",
|
||||
updated_at: "2022-03-01T16:00:06Z",
|
||||
url: "https://api.github.com/repositories/143040428/code-scanning/codeql/databases/javascript",
|
||||
},
|
||||
{
|
||||
id: 648738,
|
||||
name: "ql-database",
|
||||
language: "ql",
|
||||
uploader: {},
|
||||
content_type: "application/json; charset=utf-8",
|
||||
state: "uploaded",
|
||||
size: 39735500,
|
||||
created_at: "2022-02-02T09:38:50Z",
|
||||
updated_at: "2022-02-02T09:38:51Z",
|
||||
url: "https://api.github.com/repositories/143040428/code-scanning/codeql/databases/ql",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
quickPickSpy = jest
|
||||
.spyOn(window, "showQuickPick")
|
||||
.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("should convert a GitHub nwo to a database url", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
quickPickSpy.mockResolvedValue(
|
||||
mockedQuickPickItem({
|
||||
label: "JavaScript",
|
||||
language: "javascript",
|
||||
}),
|
||||
);
|
||||
const githubRepo = "github/codeql";
|
||||
const result = await convertGithubNwoToDatabaseUrl(
|
||||
githubRepo,
|
||||
octokit,
|
||||
progressSpy,
|
||||
);
|
||||
expect(result).toBeDefined();
|
||||
if (result === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { databaseUrl, name, owner } = result;
|
||||
|
||||
expect(databaseUrl).toBe(
|
||||
"https://api.github.com/repositories/143040428/code-scanning/codeql/databases/javascript",
|
||||
);
|
||||
expect(name).toBe("codeql");
|
||||
expect(owner).toBe("github");
|
||||
expect(quickPickSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
[
|
||||
expect.objectContaining({
|
||||
label: "C#",
|
||||
description: "csharp",
|
||||
language: "csharp",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
label: "JavaScript",
|
||||
description: "javascript",
|
||||
language: "javascript",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
label: "ql",
|
||||
description: "ql",
|
||||
language: "ql",
|
||||
}),
|
||||
],
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
// Repository doesn't exist, or the user has no access to the repository.
|
||||
it("should fail on an invalid/inaccessible repository", async () => {
|
||||
const mockApiResponse = {
|
||||
data: {
|
||||
message: "Not Found",
|
||||
},
|
||||
status: 404,
|
||||
};
|
||||
mockListCodeqlDatabases.mockResolvedValue(mockApiResponse);
|
||||
const githubRepo = "foo/bar-not-real";
|
||||
await expect(
|
||||
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
|
||||
).rejects.toThrow(/Unable to get database/);
|
||||
expect(progressSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
// User has access to the repository, but there are no databases for any language.
|
||||
it("should fail on a repository with no databases", async () => {
|
||||
const mockApiResponse = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
mockListCodeqlDatabases.mockResolvedValue(mockApiResponse);
|
||||
const githubRepo = "foo/bar-with-no-dbs";
|
||||
await expect(
|
||||
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
|
||||
).rejects.toThrow(/Unable to get database/);
|
||||
expect(progressSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("when language is already provided", () => {
|
||||
describe("when language is valid", () => {
|
||||
it("should not prompt the user", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
const githubRepo = "github/codeql";
|
||||
await convertGithubNwoToDatabaseUrl(
|
||||
githubRepo,
|
||||
octokit,
|
||||
progressSpy,
|
||||
"javascript",
|
||||
);
|
||||
expect(quickPickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when language is invalid", () => {
|
||||
it("should prompt for language", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
const githubRepo = "github/codeql";
|
||||
await convertGithubNwoToDatabaseUrl(
|
||||
githubRepo,
|
||||
octokit,
|
||||
progressSpy,
|
||||
"invalid-language",
|
||||
);
|
||||
expect(quickPickSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when language is not provided", () => {
|
||||
it("should prompt for language", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
const githubRepo = "github/codeql";
|
||||
await convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy);
|
||||
expect(quickPickSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findDirWithFile", () => {
|
||||
let dir: DirResult;
|
||||
beforeEach(() => {
|
||||
dir = dirSync({ unsafeCleanup: true });
|
||||
createFile("a");
|
||||
createFile("b");
|
||||
createFile("c");
|
||||
|
||||
createDir("dir1");
|
||||
createFile("dir1", "d");
|
||||
createFile("dir1", "e");
|
||||
createFile("dir1", "f");
|
||||
|
||||
createDir("dir2");
|
||||
createFile("dir2", "g");
|
||||
createFile("dir2", "h");
|
||||
createFile("dir2", "i");
|
||||
|
||||
createDir("dir2", "dir3");
|
||||
createFile("dir2", "dir3", "j");
|
||||
createFile("dir2", "dir3", "k");
|
||||
createFile("dir2", "dir3", "l");
|
||||
});
|
||||
|
||||
it("should find files", async () => {
|
||||
expect(await findDirWithFile(dir.name, "k")).toBe(
|
||||
join(dir.name, "dir2", "dir3"),
|
||||
);
|
||||
expect(await findDirWithFile(dir.name, "h")).toBe(join(dir.name, "dir2"));
|
||||
expect(await findDirWithFile(dir.name, "z", "a")).toBe(dir.name);
|
||||
// there's some slight indeterminism when more than one name exists
|
||||
// but in general, this will find files in the current directory before
|
||||
// finding files in sub-dirs
|
||||
expect(await findDirWithFile(dir.name, "k", "a")).toBe(dir.name);
|
||||
});
|
||||
|
||||
it("should not find files", async () => {
|
||||
expect(await findDirWithFile(dir.name, "x", "y", "z")).toBeUndefined();
|
||||
});
|
||||
|
||||
function createFile(...segments: string[]) {
|
||||
createFileSync(join(dir.name, ...segments));
|
||||
}
|
||||
|
||||
function createDir(...segments: string[]) {
|
||||
mkdirSync(join(dir.name, ...segments));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,19 @@
|
||||
import {
|
||||
mockedObject,
|
||||
mockedOctokitFunction,
|
||||
mockedQuickPickItem,
|
||||
} from "../../../utils/mocking.helpers";
|
||||
import type { GitHubDatabaseConfig } from "../../../../../src/config";
|
||||
import * as dialog from "../../../../../src/common/vscode/dialog";
|
||||
import { listDatabases } from "../../../../../src/databases/github-databases/api";
|
||||
import {
|
||||
convertGithubNwoToDatabaseUrl,
|
||||
listDatabases,
|
||||
} from "../../../../../src/databases/github-databases/api";
|
||||
import type { Credentials } from "../../../../../src/common/authentication";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
import { AppOctokit } from "../../../../../src/common/octokit";
|
||||
import { RequestError } from "@octokit/request-error";
|
||||
import { window } from "vscode";
|
||||
|
||||
// Mock the AppOctokit constructor to ensure we aren't making any network requests
|
||||
jest.mock("../../../../../src/common/octokit", () => ({
|
||||
@@ -349,3 +354,186 @@ describe("listDatabases", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("convertGithubNwoToDatabaseUrl", () => {
|
||||
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
|
||||
const progressSpy = jest.fn();
|
||||
const mockListCodeqlDatabases = mockedOctokitFunction<
|
||||
"codeScanning",
|
||||
"listCodeqlDatabases"
|
||||
>();
|
||||
const octokit = mockedObject<Octokit>({
|
||||
rest: {
|
||||
codeScanning: {
|
||||
listCodeqlDatabases: mockListCodeqlDatabases,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// We can't make the real octokit request (since we need credentials), so we mock the response.
|
||||
const successfullMockApiResponse = {
|
||||
data: [
|
||||
{
|
||||
id: 1495869,
|
||||
name: "csharp-database",
|
||||
language: "csharp",
|
||||
uploader: {},
|
||||
content_type: "application/zip",
|
||||
state: "uploaded",
|
||||
size: 55599715,
|
||||
created_at: "2022-03-24T10:46:24Z",
|
||||
updated_at: "2022-03-24T10:46:27Z",
|
||||
url: "https://api.github.com/repositories/143040428/code-scanning/codeql/databases/csharp",
|
||||
},
|
||||
{
|
||||
id: 1100671,
|
||||
name: "database.zip",
|
||||
language: "javascript",
|
||||
uploader: {},
|
||||
content_type: "application/zip",
|
||||
state: "uploaded",
|
||||
size: 29294434,
|
||||
created_at: "2022-03-01T16:00:04Z",
|
||||
updated_at: "2022-03-01T16:00:06Z",
|
||||
url: "https://api.github.com/repositories/143040428/code-scanning/codeql/databases/javascript",
|
||||
},
|
||||
{
|
||||
id: 648738,
|
||||
name: "ql-database",
|
||||
language: "ql",
|
||||
uploader: {},
|
||||
content_type: "application/json; charset=utf-8",
|
||||
state: "uploaded",
|
||||
size: 39735500,
|
||||
created_at: "2022-02-02T09:38:50Z",
|
||||
updated_at: "2022-02-02T09:38:51Z",
|
||||
url: "https://api.github.com/repositories/143040428/code-scanning/codeql/databases/ql",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
quickPickSpy = jest
|
||||
.spyOn(window, "showQuickPick")
|
||||
.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("should convert a GitHub nwo to a database url", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
quickPickSpy.mockResolvedValue(
|
||||
mockedQuickPickItem({
|
||||
label: "JavaScript",
|
||||
language: "javascript",
|
||||
}),
|
||||
);
|
||||
const githubRepo = "github/codeql";
|
||||
const result = await convertGithubNwoToDatabaseUrl(
|
||||
githubRepo,
|
||||
octokit,
|
||||
progressSpy,
|
||||
);
|
||||
expect(result).toBeDefined();
|
||||
if (result === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { databaseUrl, name, owner } = result;
|
||||
|
||||
expect(databaseUrl).toBe(
|
||||
"https://api.github.com/repositories/143040428/code-scanning/codeql/databases/javascript",
|
||||
);
|
||||
expect(name).toBe("codeql");
|
||||
expect(owner).toBe("github");
|
||||
expect(quickPickSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
[
|
||||
expect.objectContaining({
|
||||
label: "C#",
|
||||
description: "csharp",
|
||||
language: "csharp",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
label: "JavaScript",
|
||||
description: "javascript",
|
||||
language: "javascript",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
label: "ql",
|
||||
description: "ql",
|
||||
language: "ql",
|
||||
}),
|
||||
],
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
// Repository doesn't exist, or the user has no access to the repository.
|
||||
it("should fail on an invalid/inaccessible repository", async () => {
|
||||
const mockApiResponse = {
|
||||
data: {
|
||||
message: "Not Found",
|
||||
},
|
||||
status: 404,
|
||||
};
|
||||
mockListCodeqlDatabases.mockResolvedValue(mockApiResponse);
|
||||
const githubRepo = "foo/bar-not-real";
|
||||
await expect(
|
||||
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
|
||||
).rejects.toThrow(/Unable to get database/);
|
||||
expect(progressSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
// User has access to the repository, but there are no databases for any language.
|
||||
it("should fail on a repository with no databases", async () => {
|
||||
const mockApiResponse = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
mockListCodeqlDatabases.mockResolvedValue(mockApiResponse);
|
||||
const githubRepo = "foo/bar-with-no-dbs";
|
||||
await expect(
|
||||
convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy),
|
||||
).rejects.toThrow(/Unable to get database/);
|
||||
expect(progressSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("when language is already provided", () => {
|
||||
describe("when language is valid", () => {
|
||||
it("should not prompt the user", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
const githubRepo = "github/codeql";
|
||||
await convertGithubNwoToDatabaseUrl(
|
||||
githubRepo,
|
||||
octokit,
|
||||
progressSpy,
|
||||
"javascript",
|
||||
);
|
||||
expect(quickPickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when language is invalid", () => {
|
||||
it("should prompt for language", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
const githubRepo = "github/codeql";
|
||||
await convertGithubNwoToDatabaseUrl(
|
||||
githubRepo,
|
||||
octokit,
|
||||
progressSpy,
|
||||
"invalid-language",
|
||||
);
|
||||
expect(quickPickSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when language is not provided", () => {
|
||||
it("should prompt for language", async () => {
|
||||
mockListCodeqlDatabases.mockResolvedValue(successfullMockApiResponse);
|
||||
const githubRepo = "github/codeql";
|
||||
await convertGithubNwoToDatabaseUrl(githubRepo, octokit, progressSpy);
|
||||
expect(quickPickSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import type { Uri, Webview, WebviewView } from "vscode";
|
||||
import { EventEmitter } from "vscode";
|
||||
import type { ModelConfigListener } from "../../../../../src/config";
|
||||
import { MethodModelingViewProvider } from "../../../../../src/model-editor/method-modeling/method-modeling-view-provider";
|
||||
import { createMockApp } from "../../../../__mocks__/appMock";
|
||||
import { createMockModelingEvents } from "../../../../__mocks__/model-editor/modelingEventsMock";
|
||||
import { createMockModelingStore } from "../../../../__mocks__/model-editor/modelingStoreMock";
|
||||
import { mockedObject } from "../../../../mocked-object";
|
||||
import type { FromMethodModelingMessage } from "../../../../../src/common/interface-types";
|
||||
import { DisposableObject } from "../../../../../src/common/disposable-object";
|
||||
import type { ModelingEvents } from "../../../../../src/model-editor/modeling-events";
|
||||
import type {
|
||||
DbModelingState,
|
||||
ModelingStore,
|
||||
SelectedMethodDetails,
|
||||
} from "../../../../../src/model-editor/modeling-store";
|
||||
import { mockDatabaseItem } from "../../../utils/mocking.helpers";
|
||||
import {
|
||||
createMethod,
|
||||
createUsage,
|
||||
} from "../../../../factories/model-editor/method-factories";
|
||||
|
||||
describe("method modeling view provider", () => {
|
||||
// Modeling store
|
||||
let getStateForActiveDb: jest.MockedFunction<
|
||||
ModelingStore["getStateForActiveDb"]
|
||||
>;
|
||||
let getSelectedMethodDetails: jest.MockedFunction<
|
||||
ModelingStore["getSelectedMethodDetails"]
|
||||
>;
|
||||
|
||||
// Modeling events
|
||||
let selectedMethodChangedEventEmitter: ModelingEvents["onSelectedMethodChangedEventEmitter"];
|
||||
let dbOpenedEventEmitter: ModelingEvents["onDbOpenedEventEmitter"];
|
||||
|
||||
// View provider
|
||||
let viewProvider: MethodModelingViewProvider;
|
||||
let onDidReceiveMessage: (msg: FromMethodModelingMessage) => Promise<void>;
|
||||
let postMessage: (message: unknown) => Promise<boolean>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app = createMockApp({});
|
||||
|
||||
getStateForActiveDb = jest.fn().mockReturnValue(undefined);
|
||||
getSelectedMethodDetails = jest.fn().mockReturnValue(undefined);
|
||||
const modelingStore = createMockModelingStore({
|
||||
getStateForActiveDb,
|
||||
getSelectedMethodDetails,
|
||||
});
|
||||
|
||||
selectedMethodChangedEventEmitter = new EventEmitter();
|
||||
dbOpenedEventEmitter = new EventEmitter();
|
||||
const modelingEvents = createMockModelingEvents({
|
||||
onSelectedMethodChanged: selectedMethodChangedEventEmitter.event,
|
||||
onDbOpened: dbOpenedEventEmitter.event,
|
||||
});
|
||||
|
||||
const modelConfigListener = mockedObject<ModelConfigListener>({
|
||||
showTypeModels: true,
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
});
|
||||
|
||||
viewProvider = new MethodModelingViewProvider(
|
||||
app,
|
||||
modelingStore,
|
||||
modelingEvents,
|
||||
modelConfigListener,
|
||||
);
|
||||
|
||||
postMessage = jest.fn().mockResolvedValue(true);
|
||||
const webview: Webview = {
|
||||
options: {},
|
||||
html: "",
|
||||
onDidReceiveMessage: (listener) => {
|
||||
onDidReceiveMessage = listener;
|
||||
return new DisposableObject();
|
||||
},
|
||||
postMessage,
|
||||
asWebviewUri: (uri: Uri) => uri,
|
||||
cspSource: "",
|
||||
};
|
||||
|
||||
const webviewView = mockedObject<WebviewView>({
|
||||
webview,
|
||||
onDidDispose: jest.fn(),
|
||||
});
|
||||
|
||||
viewProvider.resolveWebviewView(webviewView);
|
||||
|
||||
expect(onDidReceiveMessage).toBeDefined();
|
||||
});
|
||||
|
||||
it("should load webview when no active DB", async () => {
|
||||
await onDidReceiveMessage({
|
||||
t: "viewLoaded",
|
||||
viewName: MethodModelingViewProvider.viewType,
|
||||
});
|
||||
|
||||
expect(postMessage).toHaveBeenCalledTimes(1);
|
||||
expect(postMessage).toHaveBeenCalledWith({
|
||||
t: "setMethodModelingPanelViewState",
|
||||
viewState: {
|
||||
language: undefined,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should load webview when active DB but no selected method", async () => {
|
||||
const dbModelingState = mockedObject<DbModelingState>({
|
||||
databaseItem: mockDatabaseItem({
|
||||
language: "java",
|
||||
}),
|
||||
});
|
||||
getStateForActiveDb.mockReturnValue(dbModelingState);
|
||||
|
||||
await onDidReceiveMessage({
|
||||
t: "viewLoaded",
|
||||
viewName: MethodModelingViewProvider.viewType,
|
||||
});
|
||||
|
||||
expect(postMessage).toHaveBeenCalledTimes(3);
|
||||
expect(postMessage).toHaveBeenNthCalledWith(1, {
|
||||
t: "setMethodModelingPanelViewState",
|
||||
viewState: {
|
||||
language: undefined,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(postMessage).toHaveBeenNthCalledWith(2, {
|
||||
t: "setInModelingMode",
|
||||
inModelingMode: true,
|
||||
});
|
||||
expect(postMessage).toHaveBeenNthCalledWith(3, {
|
||||
t: "setMethodModelingPanelViewState",
|
||||
viewState: {
|
||||
language: "java",
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should load webview when active DB and a selected method", async () => {
|
||||
const dbModelingState = mockedObject<DbModelingState>({
|
||||
databaseItem: mockDatabaseItem({
|
||||
language: "java",
|
||||
}),
|
||||
});
|
||||
getStateForActiveDb.mockReturnValue(dbModelingState);
|
||||
|
||||
const selectedMethodDetails: SelectedMethodDetails = {
|
||||
databaseItem: dbModelingState.databaseItem,
|
||||
method: createMethod(),
|
||||
usage: createUsage(),
|
||||
modeledMethods: [],
|
||||
isModified: false,
|
||||
isInProgress: false,
|
||||
processedByAutoModel: false,
|
||||
};
|
||||
getSelectedMethodDetails.mockReturnValue(selectedMethodDetails);
|
||||
|
||||
await onDidReceiveMessage({
|
||||
t: "viewLoaded",
|
||||
viewName: MethodModelingViewProvider.viewType,
|
||||
});
|
||||
|
||||
expect(postMessage).toHaveBeenCalledTimes(4);
|
||||
expect(postMessage).toHaveBeenNthCalledWith(1, {
|
||||
t: "setMethodModelingPanelViewState",
|
||||
viewState: {
|
||||
language: undefined,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(postMessage).toHaveBeenNthCalledWith(2, {
|
||||
t: "setInModelingMode",
|
||||
inModelingMode: true,
|
||||
});
|
||||
expect(postMessage).toHaveBeenNthCalledWith(3, {
|
||||
t: "setMethodModelingPanelViewState",
|
||||
viewState: {
|
||||
language: "java",
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(postMessage).toHaveBeenNthCalledWith(4, {
|
||||
t: "setSelectedMethod",
|
||||
method: selectedMethodDetails.method,
|
||||
modeledMethods: selectedMethodDetails.modeledMethods,
|
||||
isModified: selectedMethodDetails.isModified,
|
||||
isInProgress: selectedMethodDetails.isInProgress,
|
||||
processedByAutoModel: selectedMethodDetails.processedByAutoModel,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user