Skip to content

Commit bc10a7a

Browse files
authored
Merge pull request #1663 from github/koesie10/gh-api-client-msw-test
Add msw tests for gh-api-client
2 parents b156268 + 5629865 commit bc10a7a

File tree

9 files changed

+370
-152
lines changed

9 files changed

+370
-152
lines changed

.vscode/launch.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,8 @@
4444
"bdd",
4545
"--colors",
4646
"--diff",
47-
"-r",
48-
"ts-node/register",
49-
"-r",
50-
"test/mocha.setup.js",
47+
"--config",
48+
".mocharc.json",
5149
"test/pure-tests/**/*.ts"
5250
],
5351
"stopOnEntry": false,

extensions/ql-vscode/.mocharc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"exit": true,
3+
"require": [
4+
"test/mocha.setup.js"
5+
]
6+
}

extensions/ql-vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@
12481248
"watch:extension": "tsc --watch",
12491249
"watch:webpack": "gulp watchView",
12501250
"test": "npm-run-all -p test:*",
1251-
"test:unit": "mocha --exit -r ts-node/register -r test/mocha.setup.js test/pure-tests/**/*.ts",
1251+
"test:unit": "mocha --config .mocharc.json test/pure-tests/**/*.ts",
12521252
"test:view": "jest",
12531253
"preintegration": "rm -rf ./out/vscode-tests && gulp",
12541254
"integration": "node ./out/vscode-tests/run-integration-tests.js no-workspace,minimal-workspace",

extensions/ql-vscode/src/extension.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ import {
116116
} from './remote-queries/gh-api/variant-analysis';
117117
import { VariantAnalysisManager } from './remote-queries/variant-analysis-manager';
118118
import { createVariantAnalysisContentProvider } from './remote-queries/variant-analysis-content-provider';
119-
import { MockGitHubApiServer } from './mocks/mock-gh-api-server';
119+
import { VSCodeMockGitHubApiServer } from './mocks/vscode-mock-gh-api-server';
120120
import { VariantAnalysisResultsManager } from './remote-queries/variant-analysis-results-manager';
121121

122122
/**
@@ -1194,7 +1194,7 @@ async function activateWithInstalledDistribution(
11941194
)
11951195
);
11961196

1197-
const mockServer = new MockGitHubApiServer(ctx);
1197+
const mockServer = new VSCodeMockGitHubApiServer(ctx);
11981198
ctx.subscriptions.push(mockServer);
11991199
ctx.subscriptions.push(
12001200
commandRunner(
Lines changed: 56 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import * as path from 'path';
22
import * as fs from 'fs-extra';
3-
import { commands, env, ExtensionContext, ExtensionMode, QuickPickItem, Uri, window } from 'vscode';
43
import { setupServer, SetupServerApi } from 'msw/node';
54

6-
import { getMockGitHubApiServerScenariosPath, MockGitHubApiConfigListener } from '../config';
75
import { DisposableObject } from '../pure/disposable-object';
86

97
import { Recorder } from './recorder';
@@ -14,211 +12,128 @@ import { getDirectoryNamesInsidePath } from '../pure/files';
1412
* Enables mocking of the GitHub API server via HTTP interception, using msw.
1513
*/
1614
export class MockGitHubApiServer extends DisposableObject {
17-
private isListening: boolean;
18-
private config: MockGitHubApiConfigListener;
15+
private _isListening: boolean;
1916

2017
private readonly server: SetupServerApi;
2118
private readonly recorder: Recorder;
2219

23-
constructor(
24-
private readonly ctx: ExtensionContext,
25-
) {
20+
constructor() {
2621
super();
27-
this.isListening = false;
28-
this.config = new MockGitHubApiConfigListener();
22+
this._isListening = false;
2923

3024
this.server = setupServer();
3125
this.recorder = this.push(new Recorder(this.server));
32-
33-
this.setupConfigListener();
3426
}
3527

3628
public startServer(): void {
37-
if (this.isListening) {
29+
if (this._isListening) {
3830
return;
3931
}
4032

4133
this.server.listen();
42-
this.isListening = true;
34+
this._isListening = true;
4335
}
4436

4537
public stopServer(): void {
4638
this.server.close();
47-
this.isListening = false;
39+
this._isListening = false;
4840
}
4941

50-
public async loadScenario(): Promise<void> {
51-
const scenariosPath = await this.getScenariosPath();
42+
public async loadScenario(scenarioName: string, scenariosPath?: string): Promise<void> {
5243
if (!scenariosPath) {
53-
return;
54-
}
55-
56-
const scenarioNames = await getDirectoryNamesInsidePath(scenariosPath);
57-
const scenarioQuickPickItems = scenarioNames.map(s => ({ label: s }));
58-
const quickPickOptions = {
59-
placeHolder: 'Select a scenario to load',
60-
};
61-
const selectedScenario = await window.showQuickPick<QuickPickItem>(
62-
scenarioQuickPickItems,
63-
quickPickOptions);
64-
if (!selectedScenario) {
65-
return;
44+
scenariosPath = await this.getDefaultScenariosPath();
45+
if (!scenariosPath) {
46+
return;
47+
}
6648
}
6749

68-
const scenarioName = selectedScenario.label;
6950
const scenarioPath = path.join(scenariosPath, scenarioName);
7051

7152
const handlers = await createRequestHandlers(scenarioPath);
7253
this.server.resetHandlers();
7354
this.server.use(...handlers);
55+
}
56+
57+
public async saveScenario(scenarioName: string, scenariosPath?: string): Promise<string> {
58+
if (!scenariosPath) {
59+
scenariosPath = await this.getDefaultScenariosPath();
60+
if (!scenariosPath) {
61+
throw new Error('Could not find scenarios path');
62+
}
63+
}
64+
65+
const filePath = await this.recorder.save(scenariosPath, scenarioName);
7466

75-
// Set a value in the context to track whether we have a scenario loaded.
76-
// This allows us to use this to show/hide commands (see package.json)
77-
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.scenarioLoaded', true);
67+
await this.stopRecording();
7868

79-
await window.showInformationMessage(`Loaded scenario '${scenarioName}'`);
69+
return filePath;
8070
}
8171

8272
public async unloadScenario(): Promise<void> {
83-
if (!this.isScenarioLoaded()) {
84-
await window.showInformationMessage('No scenario currently loaded');
85-
}
86-
else {
87-
await this.unloadAllScenarios();
88-
await window.showInformationMessage('Unloaded scenario');
73+
if (!this.isScenarioLoaded) {
74+
return;
8975
}
76+
77+
await this.unloadAllScenarios();
9078
}
9179

9280
public async startRecording(): Promise<void> {
9381
if (this.recorder.isRecording) {
94-
void window.showErrorMessage('A scenario is already being recorded. Use the "Save Scenario" or "Cancel Scenario" commands to finish recording.');
9582
return;
9683
}
9784

98-
if (this.isScenarioLoaded()) {
85+
if (this.isScenarioLoaded) {
9986
await this.unloadAllScenarios();
100-
void window.showInformationMessage('A scenario was loaded so it has been unloaded');
10187
}
10288

10389
this.recorder.start();
104-
// Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json)
105-
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', true);
90+
}
10691

107-
await window.showInformationMessage('Recording scenario. To save the scenario, use the "CodeQL Mock GitHub API Server: Save Scenario" command.');
92+
public async stopRecording(): Promise<void> {
93+
await this.recorder.stop();
94+
await this.recorder.clear();
10895
}
10996

110-
public async saveScenario(): Promise<void> {
111-
const scenariosPath = await this.getScenariosPath();
97+
public async getScenarioNames(scenariosPath?: string): Promise<string[]> {
11298
if (!scenariosPath) {
113-
return;
114-
}
115-
116-
// Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json)
117-
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', false);
118-
119-
if (!this.recorder.isRecording) {
120-
void window.showErrorMessage('No scenario is currently being recorded.');
121-
return;
122-
}
123-
if (!this.recorder.anyRequestsRecorded) {
124-
void window.showWarningMessage('No requests were recorded. Cancelling scenario.');
125-
126-
await this.stopRecording();
127-
128-
return;
129-
}
130-
131-
const name = await window.showInputBox({
132-
title: 'Save scenario',
133-
prompt: 'Enter a name for the scenario.',
134-
placeHolder: 'successful-run',
135-
});
136-
if (!name) {
137-
return;
99+
scenariosPath = await this.getDefaultScenariosPath();
100+
if (!scenariosPath) {
101+
return [];
102+
}
138103
}
139104

140-
const filePath = await this.recorder.save(scenariosPath, name);
141-
142-
await this.stopRecording();
143-
144-
const action = await window.showInformationMessage(`Scenario saved to ${filePath}`, 'Open directory');
145-
if (action === 'Open directory') {
146-
await env.openExternal(Uri.file(filePath));
147-
}
105+
return await getDirectoryNamesInsidePath(scenariosPath);
148106
}
149107

150-
public async cancelRecording(): Promise<void> {
151-
if (!this.recorder.isRecording) {
152-
void window.showErrorMessage('No scenario is currently being recorded.');
153-
return;
154-
}
155-
156-
await this.stopRecording();
157-
158-
void window.showInformationMessage('Recording cancelled.');
108+
public get isListening(): boolean {
109+
return this._isListening;
159110
}
160111

161-
private async stopRecording(): Promise<void> {
162-
// Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json)
163-
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', false);
112+
public get isRecording(): boolean {
113+
return this.recorder.isRecording;
114+
}
164115

165-
await this.recorder.stop();
166-
await this.recorder.clear();
116+
public get anyRequestsRecorded(): boolean {
117+
return this.recorder.anyRequestsRecorded;
167118
}
168119

169-
private async getScenariosPath(): Promise<string | undefined> {
170-
const scenariosPath = getMockGitHubApiServerScenariosPath();
171-
if (scenariosPath) {
172-
return scenariosPath;
173-
}
120+
public get isScenarioLoaded(): boolean {
121+
return this.server.listHandlers().length > 0;
122+
}
174123

175-
if (this.ctx.extensionMode === ExtensionMode.Development) {
176-
const developmentScenariosPath = Uri.joinPath(this.ctx.extensionUri, 'src/mocks/scenarios').fsPath.toString();
177-
if (await fs.pathExists(developmentScenariosPath)) {
178-
return developmentScenariosPath;
179-
}
180-
}
124+
public async getDefaultScenariosPath(): Promise<string | undefined> {
125+
// This should be the directory where package.json is located
126+
const rootDirectory = path.resolve(__dirname, '../..');
181127

182-
const directories = await window.showOpenDialog({
183-
canSelectFolders: true,
184-
canSelectFiles: false,
185-
canSelectMany: false,
186-
openLabel: 'Select scenarios directory',
187-
title: 'Select scenarios directory',
188-
});
189-
if (directories === undefined || directories.length === 0) {
190-
void window.showErrorMessage('No scenarios directory selected.');
191-
return undefined;
128+
const scenariosPath = path.resolve(rootDirectory, 'src/mocks/scenarios');
129+
if (await fs.pathExists(scenariosPath)) {
130+
return scenariosPath;
192131
}
193132

194-
// Unfortunately, we cannot save the directory in the configuration because that requires
195-
// the configuration to be registered. If we do that, it would be visible to all users; there
196-
// is no "when" clause that would allow us to only show it to users who have enabled the feature flag.
197-
198-
return directories[0].fsPath;
199-
}
200-
201-
private isScenarioLoaded(): boolean {
202-
return this.server.listHandlers().length > 0;
133+
return undefined;
203134
}
204135

205136
private async unloadAllScenarios(): Promise<void> {
206137
this.server.resetHandlers();
207-
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.scenarioLoaded', false);
208-
}
209-
210-
private setupConfigListener(): void {
211-
// The config "changes" from the default at startup, so we need to call onConfigChange() to ensure the server is
212-
// started if required.
213-
this.onConfigChange();
214-
this.config.onDidChangeConfiguration(() => this.onConfigChange());
215-
}
216-
217-
private onConfigChange(): void {
218-
if (this.config.mockServerEnabled && !this.isListening) {
219-
this.startServer();
220-
} else if (!this.config.mockServerEnabled && this.isListening) {
221-
this.stopServer();
222-
}
223138
}
224139
}

0 commit comments

Comments
 (0)