Add tests for external API query
This adds tests for the external API query and retrieving of results. It does not use the "real" CLI integration, but instead mocks the CLI server and query runner. To make mocking easier and require less type casting, I've narrowed some of the arguments of some other functions. They now use `Pick` to only require the properties they need.
This commit is contained in:
@@ -21,8 +21,8 @@ import { redactableError } from "../pure/errors";
|
||||
import { QLPACK_FILENAMES } from "../pure/ql";
|
||||
|
||||
export async function qlpackOfDatabase(
|
||||
cli: CodeQLCliServer,
|
||||
db: DatabaseItem,
|
||||
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||
db: Pick<DatabaseItem, "contents">,
|
||||
): Promise<QlPacksForLanguage> {
|
||||
if (db.contents === undefined) {
|
||||
throw new Error("Database is invalid and cannot infer QLPack.");
|
||||
|
||||
@@ -13,6 +13,7 @@ import { decodeBqrsToExternalApiUsages } from "./bqrs";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { getResults, runQuery } from "./external-api-usage-query";
|
||||
import { extLogger } from "../common";
|
||||
|
||||
export class DataExtensionsEditorView extends AbstractWebview<
|
||||
ToDataExtensionsEditorMessage,
|
||||
@@ -77,6 +78,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
queryRunner: this.queryRunner,
|
||||
databaseItem: this.databaseItem,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
logger: extLogger,
|
||||
progress: (progressUpdate: ProgressUpdate) => {
|
||||
void this.showProgress(progressUpdate, 1500);
|
||||
},
|
||||
@@ -96,6 +98,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
const bqrsChunk = await getResults({
|
||||
cliServer: this.cliServer,
|
||||
bqrsPath: queryResult.outputDir.bqrsPath,
|
||||
logger: extLogger,
|
||||
});
|
||||
if (!bqrsChunk) {
|
||||
await this.clearProgress();
|
||||
|
||||
@@ -4,17 +4,18 @@ import { file } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { getOnDiskWorkspaceFolders } from "../helpers";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
import { Logger, TeeLogger } from "../common";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { ProgressCallback } from "../progress";
|
||||
|
||||
export type RunQueryOptions = {
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveQueriesInSuite">;
|
||||
queryRunner: Pick<QueryRunner, "createQueryRun" | "logger">;
|
||||
databaseItem: Pick<DatabaseItem, "contents" | "databaseUri" | "language">;
|
||||
queryStorageDir: string;
|
||||
logger: Logger;
|
||||
|
||||
progress: ProgressCallback;
|
||||
token: CancellationToken;
|
||||
@@ -25,6 +26,7 @@ export async function runQuery({
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryStorageDir,
|
||||
logger,
|
||||
progress,
|
||||
token,
|
||||
}: RunQueryOptions): Promise<CoreCompletedQuery | undefined> {
|
||||
@@ -58,7 +60,7 @@ export async function runQuery({
|
||||
);
|
||||
|
||||
if (queries.length !== 1) {
|
||||
void extLogger.log(`Expected exactly one query, got ${queries.length}`);
|
||||
void logger.log(`Expected exactly one query, got ${queries.length}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,14 +85,19 @@ export async function runQuery({
|
||||
}
|
||||
|
||||
export type GetResultsOptions = {
|
||||
cliServer: CodeQLCliServer;
|
||||
cliServer: Pick<CodeQLCliServer, "bqrsInfo" | "bqrsDecode">;
|
||||
bqrsPath: string;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
export async function getResults({ cliServer, bqrsPath }: GetResultsOptions) {
|
||||
export async function getResults({
|
||||
cliServer,
|
||||
bqrsPath,
|
||||
logger,
|
||||
}: GetResultsOptions) {
|
||||
const bqrsInfo = await cliServer.bqrsInfo(bqrsPath);
|
||||
if (bqrsInfo["result-sets"].length !== 1) {
|
||||
void extLogger.log(
|
||||
void logger.log(
|
||||
`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`,
|
||||
);
|
||||
return undefined;
|
||||
@@ -98,5 +105,7 @@ export async function getResults({ cliServer, bqrsPath }: GetResultsOptions) {
|
||||
|
||||
const resultSet = bqrsInfo["result-sets"][0];
|
||||
|
||||
return cliServer.bqrsDecode(bqrsPath, resultSet.name);
|
||||
const result = await cliServer.bqrsDecode(bqrsPath, resultSet.name);
|
||||
void logger.log(JSON.stringify(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -478,7 +478,7 @@ function findStandardQueryPack(
|
||||
}
|
||||
|
||||
export async function getQlPackForDbscheme(
|
||||
cliServer: CodeQLCliServer,
|
||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||
dbschemePath: string,
|
||||
): Promise<QlPacksForLanguage> {
|
||||
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
import {
|
||||
getResults,
|
||||
runQuery,
|
||||
} from "../../../../src/data-extensions-editor/external-api-usage-query";
|
||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||
import type { Uri } from "vscode";
|
||||
import { DatabaseKind } from "../../../../src/local-databases";
|
||||
import * as queryResolver from "../../../../src/contextual/queryResolver";
|
||||
import { file } from "tmp-promise";
|
||||
import { QueryResultType } from "../../../../src/pure/new-messages";
|
||||
import { readFile } from "fs-extra";
|
||||
import { load } from "js-yaml";
|
||||
|
||||
function createMockUri(path = "/a/b/c/foo"): Uri {
|
||||
return {
|
||||
scheme: "file",
|
||||
authority: "",
|
||||
path,
|
||||
query: "",
|
||||
fragment: "",
|
||||
fsPath: path,
|
||||
with: jest.fn(),
|
||||
toJSON: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
describe("runQuery", () => {
|
||||
it("runs the query", async () => {
|
||||
jest.spyOn(queryResolver, "qlpackOfDatabase").mockResolvedValue({
|
||||
dbschemePack: "codeql/java-all",
|
||||
dbschemePackIsLibraryPack: false,
|
||||
queryPack: "codeql/java-queries",
|
||||
});
|
||||
|
||||
const logPath = (await file()).path;
|
||||
|
||||
const options = {
|
||||
cliServer: {
|
||||
resolveQlpacks: jest
|
||||
.fn()
|
||||
.mockRejectedValue(
|
||||
new Error("Did not expect mocked method to be called"),
|
||||
),
|
||||
resolveQueriesInSuite: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
"/home/github/codeql/java/ql/src/Telemetry/FetchExternalAPIs.ql",
|
||||
]),
|
||||
},
|
||||
queryRunner: {
|
||||
createQueryRun: jest.fn().mockReturnValue({
|
||||
evaluate: jest.fn().mockResolvedValue({
|
||||
resultType: QueryResultType.SUCCESS,
|
||||
}),
|
||||
outputDir: {
|
||||
logPath,
|
||||
},
|
||||
}),
|
||||
logger: createMockLogger(),
|
||||
},
|
||||
logger: createMockLogger(),
|
||||
databaseItem: {
|
||||
databaseUri: createMockUri("/a/b/c/src.zip"),
|
||||
contents: {
|
||||
kind: DatabaseKind.Database,
|
||||
name: "foo",
|
||||
datasetUri: createMockUri(),
|
||||
},
|
||||
language: "java",
|
||||
},
|
||||
queryStorageDir: "/tmp/queries",
|
||||
progress: jest.fn(),
|
||||
token: {
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: jest.fn(),
|
||||
},
|
||||
};
|
||||
const result = await runQuery(options);
|
||||
|
||||
expect(result?.resultType).toEqual(QueryResultType.SUCCESS);
|
||||
|
||||
expect(options.cliServer.resolveQueriesInSuite).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
[],
|
||||
);
|
||||
const suiteFile = options.cliServer.resolveQueriesInSuite.mock.calls[0][0];
|
||||
const suiteFileContents = await readFile(suiteFile, "utf8");
|
||||
const suiteYaml = load(suiteFileContents);
|
||||
expect(suiteYaml).toEqual([
|
||||
{
|
||||
from: "codeql/java-all",
|
||||
queries: ".",
|
||||
include: {
|
||||
id: "java/telemetry/fetch-external-apis",
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "codeql/java-queries",
|
||||
queries: ".",
|
||||
include: {
|
||||
id: "java/telemetry/fetch-external-apis",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith(
|
||||
"/a/b/c/src.zip",
|
||||
{
|
||||
queryPath:
|
||||
"/home/github/codeql/java/ql/src/Telemetry/FetchExternalAPIs.ql",
|
||||
quickEvalPosition: undefined,
|
||||
},
|
||||
false,
|
||||
[],
|
||||
undefined,
|
||||
"/tmp/queries",
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResults", () => {
|
||||
const options = {
|
||||
cliServer: {
|
||||
bqrsInfo: jest.fn(),
|
||||
bqrsDecode: jest.fn(),
|
||||
},
|
||||
bqrsPath: "/tmp/results.bqrs",
|
||||
logger: createMockLogger(),
|
||||
};
|
||||
|
||||
it("returns undefined when there are no results", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [],
|
||||
});
|
||||
|
||||
expect(await getResults(options)).toBeUndefined();
|
||||
expect(options.logger.log).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/Expected exactly one result set/),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns undefined when there are multiple result sets", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [
|
||||
{
|
||||
name: "#select",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ name: "supported", kind: "b" },
|
||||
{ name: "usage", kind: "e" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "#select2",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ name: "supported", kind: "b" },
|
||||
{ name: "usage", kind: "e" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(await getResults(options)).toBeUndefined();
|
||||
expect(options.logger.log).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/Expected exactly one result set/),
|
||||
);
|
||||
});
|
||||
|
||||
it("gets the result set", async () => {
|
||||
options.cliServer.bqrsInfo.mockResolvedValue({
|
||||
"result-sets": [
|
||||
{
|
||||
name: "#select",
|
||||
rows: 10,
|
||||
columns: [
|
||||
{ name: "apiName", kind: "s" },
|
||||
{ name: "supported", kind: "b" },
|
||||
{ name: "usage", kind: "e" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"compatible-query-kinds": ["Table", "Tree", "Graph"],
|
||||
});
|
||||
const decodedResultSet = {
|
||||
columns: [
|
||||
{ name: "apiName", kind: "String" },
|
||||
{ name: "supported", kind: "Boolean" },
|
||||
{ name: "usage", kind: "Entity" },
|
||||
],
|
||||
tuples: [
|
||||
[
|
||||
"java.io.PrintStream#println(String)",
|
||||
true,
|
||||
{
|
||||
label: "println(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 29,
|
||||
startColumn: 9,
|
||||
endLine: 29,
|
||||
endColumn: 49,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
options.cliServer.bqrsDecode.mockResolvedValue(decodedResultSet);
|
||||
|
||||
const result = await getResults(options);
|
||||
expect(result).toEqual(decodedResultSet);
|
||||
expect(options.cliServer.bqrsInfo).toHaveBeenCalledWith(options.bqrsPath);
|
||||
expect(options.cliServer.bqrsDecode).toHaveBeenCalledWith(
|
||||
options.bqrsPath,
|
||||
"#select",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user