Skip to content

Commit f7c0d4d

Browse files
authored
Merge pull request #3160 from github/koesie10/github-databases-tests
Add tests and test plan for GitHub database download
2 parents 704895b + b3da120 commit f7c0d4d

5 files changed

Lines changed: 312 additions & 10 deletions

File tree

19.2 KB
Loading

docs/test-plan.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,24 @@ Note that this test requires the feature flag: `codeQL.model.flowGeneration`
185185
2. Click "Generate".
186186
- Check that rows are filled out.
187187

188+
### GitHub database download
189+
190+
#### Test case 1: Download a database
191+
192+
Open a clone of the [`github/codeql`](https://github.com/github/codeql) repository as a folder.
193+
194+
1. Wait a few seconds until the CodeQL extension is fully initialized.
195+
- Check that the following prompt appears:
196+
197+
![database-download-prompt](images/github-database-download-prompt.png)
198+
199+
- If the prompt does not appear, ensure that the `codeQL.githubDatabase.download` setting is not set in workspace or user settings.
200+
201+
2. Click "Download".
202+
3. Select the "C#" and "JavaScript" databases.
203+
- Check that there are separate notifications for both downloads.
204+
- Check that both databases are added when the downloads are complete.
205+
188206
### General
189207

190208
#### Test case 1: Change to a different colour theme

extensions/ql-vscode/src/databases/github-databases/github-databases-module.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import {
1212
askForGitHubDatabaseDownload,
1313
downloadDatabaseFromGitHub,
1414
} from "./download";
15-
import {
16-
GitHubDatabaseConfig,
17-
GitHubDatabaseConfigListener,
18-
} from "../../config";
15+
import { GitHubDatabaseConfig } from "../../config";
1916
import { DatabaseManager } from "../local-databases";
2017
import { CodeQLCliServer } from "../../codeql-cli/cli";
2118
import { CodeqlDatabase, listDatabases, ListDatabasesResult } from "./api";
@@ -28,30 +25,33 @@ import {
2825
import { Octokit } from "@octokit/rest";
2926

3027
export class GitHubDatabasesModule extends DisposableObject {
31-
private readonly config: GitHubDatabaseConfig;
32-
33-
private constructor(
28+
/**
29+
* This constructor is public only for testing purposes. Please use the `initialize` method
30+
* instead.
31+
*/
32+
constructor(
3433
private readonly app: App,
3534
private readonly databaseManager: DatabaseManager,
3635
private readonly databaseStoragePath: string,
3736
private readonly cliServer: CodeQLCliServer,
37+
private readonly config: GitHubDatabaseConfig,
3838
) {
3939
super();
40-
41-
this.config = this.push(new GitHubDatabaseConfigListener());
4240
}
4341

4442
public static async initialize(
4543
app: App,
4644
databaseManager: DatabaseManager,
4745
databaseStoragePath: string,
4846
cliServer: CodeQLCliServer,
47+
config: GitHubDatabaseConfig,
4948
): Promise<GitHubDatabasesModule> {
5049
const githubDatabasesModule = new GitHubDatabasesModule(
5150
app,
5251
databaseManager,
5352
databaseStoragePath,
5453
cliServer,
54+
config,
5555
);
5656
app.subscriptions.push(githubDatabasesModule);
5757

@@ -72,7 +72,10 @@ export class GitHubDatabasesModule extends DisposableObject {
7272
});
7373
}
7474

75-
private async promptGitHubRepositoryDownload(): Promise<void> {
75+
/**
76+
* This method is public only for testing purposes.
77+
*/
78+
public async promptGitHubRepositoryDownload(): Promise<void> {
7679
if (this.config.download === "never") {
7780
return;
7881
}

extensions/ql-vscode/src/extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { CliVersionConstraint, CodeQLCliServer } from "./codeql-cli/cli";
2828
import {
2929
CliConfigListener,
3030
DistributionConfigListener,
31+
GitHubDatabaseConfigListener,
3132
isCanary,
3233
joinOrderWarningThreshold,
3334
QueryHistoryConfigListener,
@@ -867,11 +868,14 @@ async function activateWithInstalledDistribution(
867868
),
868869
);
869870

871+
const githubDatabaseConfigListener = new GitHubDatabaseConfigListener();
872+
870873
await GitHubDatabasesModule.initialize(
871874
app,
872875
dbm,
873876
getContextStoragePath(ctx),
874877
cliServer,
878+
githubDatabaseConfigListener,
875879
);
876880

877881
void extLogger.log("Initializing query history.");
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import { window } from "vscode";
2+
import { Octokit } from "@octokit/rest";
3+
import { createMockApp } from "../../../../__mocks__/appMock";
4+
import { App } from "../../../../../src/common/app";
5+
import { DatabaseManager } from "../../../../../src/databases/local-databases";
6+
import { mockEmptyDatabaseManager } from "../../query-testing/test-runner-helpers";
7+
import { CodeQLCliServer } from "../../../../../src/codeql-cli/cli";
8+
import { mockDatabaseItem, mockedObject } from "../../../utils/mocking.helpers";
9+
import { GitHubDatabaseConfig } from "../../../../../src/config";
10+
import { GitHubDatabasesModule } from "../../../../../src/databases/github-databases";
11+
import { ValueResult } from "../../../../../src/common/value-result";
12+
import { CodeqlDatabase } from "../../../../../src/databases/github-databases/api";
13+
14+
import * as githubRepositoryFinder from "../../../../../src/databases/github-repository-finder";
15+
import * as githubDatabasesApi from "../../../../../src/databases/github-databases/api";
16+
import * as githubDatabasesDownload from "../../../../../src/databases/github-databases/download";
17+
import * as githubDatabasesUpdates from "../../../../../src/databases/github-databases/updates";
18+
import { DatabaseUpdate } from "../../../../../src/databases/github-databases/updates";
19+
20+
describe("GitHubDatabasesModule", () => {
21+
describe("promptGitHubRepositoryDownload", () => {
22+
let app: App;
23+
let databaseManager: DatabaseManager;
24+
let databaseStoragePath: string;
25+
let cliServer: CodeQLCliServer;
26+
let config: GitHubDatabaseConfig;
27+
let gitHubDatabasesModule: GitHubDatabasesModule;
28+
29+
const owner = "github";
30+
const repo = "vscode-codeql";
31+
32+
const databases: CodeqlDatabase[] = [
33+
mockedObject<CodeqlDatabase>({}),
34+
mockedObject<CodeqlDatabase>({}),
35+
];
36+
37+
let octokit: Octokit;
38+
39+
let findGitHubRepositoryForWorkspaceSpy: jest.SpiedFunction<
40+
typeof githubRepositoryFinder.findGitHubRepositoryForWorkspace
41+
>;
42+
let listDatabasesSpy: jest.SpiedFunction<
43+
typeof githubDatabasesApi.listDatabases
44+
>;
45+
let askForGitHubDatabaseDownloadSpy: jest.SpiedFunction<
46+
typeof githubDatabasesDownload.askForGitHubDatabaseDownload
47+
>;
48+
let downloadDatabaseFromGitHubSpy: jest.SpiedFunction<
49+
typeof githubDatabasesDownload.downloadDatabaseFromGitHub
50+
>;
51+
let isNewerDatabaseAvailableSpy: jest.SpiedFunction<
52+
typeof githubDatabasesUpdates.isNewerDatabaseAvailable
53+
>;
54+
let askForGitHubDatabaseUpdateSpy: jest.SpiedFunction<
55+
typeof githubDatabasesUpdates.askForGitHubDatabaseUpdate
56+
>;
57+
let downloadDatabaseUpdateFromGitHubSpy: jest.SpiedFunction<
58+
typeof githubDatabasesUpdates.downloadDatabaseUpdateFromGitHub
59+
>;
60+
let showInformationMessageSpy: jest.SpiedFunction<
61+
typeof window.showInformationMessage
62+
>;
63+
64+
beforeEach(() => {
65+
app = createMockApp();
66+
databaseManager = mockEmptyDatabaseManager();
67+
databaseStoragePath = "/a/b/some-path";
68+
cliServer = mockedObject<CodeQLCliServer>({});
69+
config = mockedObject<GitHubDatabaseConfig>({
70+
download: "ask",
71+
update: "ask",
72+
});
73+
74+
gitHubDatabasesModule = new GitHubDatabasesModule(
75+
app,
76+
databaseManager,
77+
databaseStoragePath,
78+
cliServer,
79+
config,
80+
);
81+
82+
octokit = mockedObject<Octokit>({});
83+
84+
findGitHubRepositoryForWorkspaceSpy = jest
85+
.spyOn(githubRepositoryFinder, "findGitHubRepositoryForWorkspace")
86+
.mockResolvedValue(ValueResult.ok({ owner, name: repo }));
87+
88+
listDatabasesSpy = jest
89+
.spyOn(githubDatabasesApi, "listDatabases")
90+
.mockResolvedValue({
91+
promptedForCredentials: false,
92+
databases,
93+
octokit,
94+
});
95+
96+
askForGitHubDatabaseDownloadSpy = jest
97+
.spyOn(githubDatabasesDownload, "askForGitHubDatabaseDownload")
98+
.mockRejectedValue(new Error("Not implemented"));
99+
downloadDatabaseFromGitHubSpy = jest
100+
.spyOn(githubDatabasesDownload, "downloadDatabaseFromGitHub")
101+
.mockRejectedValue(new Error("Not implemented"));
102+
isNewerDatabaseAvailableSpy = jest
103+
.spyOn(githubDatabasesUpdates, "isNewerDatabaseAvailable")
104+
.mockImplementation(() => {
105+
throw new Error("Not implemented");
106+
});
107+
askForGitHubDatabaseUpdateSpy = jest
108+
.spyOn(githubDatabasesUpdates, "askForGitHubDatabaseUpdate")
109+
.mockRejectedValue(new Error("Not implemented"));
110+
downloadDatabaseUpdateFromGitHubSpy = jest
111+
.spyOn(githubDatabasesUpdates, "downloadDatabaseUpdateFromGitHub")
112+
.mockRejectedValue(new Error("Not implemented"));
113+
114+
showInformationMessageSpy = jest
115+
.spyOn(window, "showInformationMessage")
116+
.mockResolvedValue(undefined);
117+
});
118+
119+
it("does nothing if the download config is set to never", async () => {
120+
config = mockedObject<GitHubDatabaseConfig>({
121+
download: "never",
122+
});
123+
124+
gitHubDatabasesModule = new GitHubDatabasesModule(
125+
app,
126+
databaseManager,
127+
databaseStoragePath,
128+
cliServer,
129+
config,
130+
);
131+
132+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
133+
134+
expect(findGitHubRepositoryForWorkspaceSpy).not.toHaveBeenCalled();
135+
});
136+
137+
it("does nothing if there is no GitHub repository", async () => {
138+
findGitHubRepositoryForWorkspaceSpy.mockResolvedValue(
139+
ValueResult.fail(["some error"]),
140+
);
141+
142+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
143+
});
144+
145+
it("does nothing if the user doesn't complete the download", async () => {
146+
listDatabasesSpy.mockResolvedValue(undefined);
147+
148+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
149+
});
150+
151+
it("does not show a prompt when there are no databases and the user was not prompted for credentials", async () => {
152+
listDatabasesSpy.mockResolvedValue({
153+
promptedForCredentials: false,
154+
databases: [],
155+
octokit,
156+
});
157+
158+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
159+
160+
expect(showInformationMessageSpy).not.toHaveBeenCalled();
161+
});
162+
163+
it("shows a prompt when there are no databases and the user was prompted for credentials", async () => {
164+
listDatabasesSpy.mockResolvedValue({
165+
promptedForCredentials: true,
166+
databases: [],
167+
octokit,
168+
});
169+
170+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
171+
172+
expect(showInformationMessageSpy).toHaveBeenCalledWith(
173+
"The GitHub repository does not have any CodeQL databases.",
174+
);
175+
});
176+
177+
it("shows a prompt when there are no databases and the user was prompted for credentials", async () => {
178+
listDatabasesSpy.mockResolvedValue({
179+
promptedForCredentials: true,
180+
databases: [],
181+
octokit,
182+
});
183+
184+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
185+
186+
expect(showInformationMessageSpy).toHaveBeenCalledWith(
187+
"The GitHub repository does not have any CodeQL databases.",
188+
);
189+
});
190+
191+
it("downloads the database if the user confirms the download", async () => {
192+
isNewerDatabaseAvailableSpy.mockReturnValue({
193+
type: "noDatabase",
194+
});
195+
askForGitHubDatabaseDownloadSpy.mockResolvedValue(true);
196+
downloadDatabaseFromGitHubSpy.mockResolvedValue(undefined);
197+
198+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
199+
200+
expect(askForGitHubDatabaseDownloadSpy).toHaveBeenCalledWith(
201+
databases,
202+
config,
203+
);
204+
expect(downloadDatabaseFromGitHubSpy).toHaveBeenCalledWith(
205+
octokit,
206+
owner,
207+
repo,
208+
databases,
209+
databaseManager,
210+
databaseStoragePath,
211+
cliServer,
212+
app.commands,
213+
);
214+
});
215+
216+
it("does not perform the download if the user cancels the download", async () => {
217+
isNewerDatabaseAvailableSpy.mockReturnValue({
218+
type: "noDatabase",
219+
});
220+
askForGitHubDatabaseDownloadSpy.mockResolvedValue(false);
221+
222+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
223+
224+
expect(downloadDatabaseFromGitHubSpy).not.toHaveBeenCalled();
225+
});
226+
227+
it("updates the database if the user confirms the update", async () => {
228+
const databaseUpdates: DatabaseUpdate[] = [
229+
{
230+
database: databases[0],
231+
databaseItem: mockDatabaseItem(),
232+
},
233+
];
234+
isNewerDatabaseAvailableSpy.mockReturnValue({
235+
type: "updateAvailable",
236+
databaseUpdates,
237+
});
238+
askForGitHubDatabaseUpdateSpy.mockResolvedValue(true);
239+
downloadDatabaseUpdateFromGitHubSpy.mockResolvedValue(undefined);
240+
241+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
242+
243+
expect(askForGitHubDatabaseUpdateSpy).toHaveBeenCalledWith(
244+
databaseUpdates,
245+
config,
246+
);
247+
expect(downloadDatabaseUpdateFromGitHubSpy).toHaveBeenCalledWith(
248+
octokit,
249+
owner,
250+
repo,
251+
databaseUpdates,
252+
databaseManager,
253+
databaseStoragePath,
254+
cliServer,
255+
app.commands,
256+
);
257+
});
258+
259+
it("does not perform the update if the user cancels the update", async () => {
260+
const databaseUpdates: DatabaseUpdate[] = [
261+
{
262+
database: databases[0],
263+
databaseItem: mockDatabaseItem(),
264+
},
265+
];
266+
isNewerDatabaseAvailableSpy.mockReturnValue({
267+
type: "updateAvailable",
268+
databaseUpdates,
269+
});
270+
askForGitHubDatabaseUpdateSpy.mockResolvedValue(false);
271+
272+
await gitHubDatabasesModule.promptGitHubRepositoryDownload();
273+
274+
expect(downloadDatabaseUpdateFromGitHubSpy).not.toHaveBeenCalled();
275+
});
276+
});
277+
});

0 commit comments

Comments
 (0)