Add commands for recording of scenario

This will add the commands and the implementation in the
`MockGitHubApiServer` for the recording of a scenario.
This commit is contained in:
Koen Vlaswinkel
2022-10-19 17:20:27 +02:00
parent dcac6f56da
commit a032678f24
4 changed files with 165 additions and 7 deletions

View File

@@ -645,6 +645,18 @@
"command": "codeQL.gotoQL",
"title": "CodeQL: Go to QL Code",
"enablement": "codeql.hasQLSource"
},
{
"command": "codeQL.mockGitHubApiServer.startRecording",
"title": "CodeQL Mock GitHub API Server: Start Scenario Recording"
},
{
"command": "codeQL.mockGitHubApiServer.saveScenario",
"title": "CodeQL Mock GitHub API Server: Save Scenario"
},
{
"command": "codeQL.mockGitHubApiServer.cancelRecording",
"title": "CodeQL Mock GitHub API Server: Cancel Scenario"
}
],
"menus": {
@@ -1104,6 +1116,18 @@
{
"command": "codeQLTests.showOutputDifferences",
"when": "false"
},
{
"command": "codeQL.mockGitHubApiServer.startRecording",
"when": "config.codeQL.variantAnalysis.mockGitHubApiServer && !codeQL.mockGitHubApiServer.recording"
},
{
"command": "codeQL.mockGitHubApiServer.saveScenario",
"when": "codeQL.mockGitHubApiServer.recording"
},
{
"command": "codeQL.mockGitHubApiServer.cancelRecording",
"when": "codeQL.mockGitHubApiServer.recording"
}
],
"editor/context": [

View File

@@ -449,3 +449,9 @@ export class MockGitHubApiConfigListener extends ConfigListener implements MockG
return !!MOCK_GH_API_SERVER.getValue<boolean>();
}
}
const MOCK_GH_API_SERVER_SCENARIOS_PATH = new Setting('mockGitHubApiServerScenariosPath', REMOTE_QUERIES_SETTING);
export function getMockGitHubApiServerScenariosPath(): string | undefined {
return MOCK_GH_API_SERVER_SCENARIOS_PATH.getValue<string>();
}

View File

@@ -116,6 +116,7 @@ import {
} from './remote-queries/gh-api/variant-analysis';
import { VariantAnalysisManager } from './remote-queries/variant-analysis-manager';
import { createVariantAnalysisContentProvider } from './remote-queries/variant-analysis-content-provider';
import { MockGitHubApiServer } from './mocks/mock-gh-api-server';
/**
* extension.ts
@@ -1190,6 +1191,27 @@ async function activateWithInstalledDistribution(
)
);
const mockServer = new MockGitHubApiServer();
ctx.subscriptions.push(mockServer);
ctx.subscriptions.push(
commandRunner(
'codeQL.mockGitHubApiServer.startRecording',
async () => await mockServer.recordScenario(),
)
);
ctx.subscriptions.push(
commandRunner(
'codeQL.mockGitHubApiServer.saveScenario',
async () => await mockServer.saveScenario(),
)
);
ctx.subscriptions.push(
commandRunner(
'codeQL.mockGitHubApiServer.cancelRecording',
async () => await mockServer.cancelRecording(),
)
);
await commands.executeCommand('codeQLDatabases.removeOrphanedDatabases');
void logger.log('Successfully finished extension initialization.');

View File

@@ -1,28 +1,44 @@
import { MockGitHubApiConfigListener } from '../config';
import { setupServer, SetupServerApi } from 'msw/node';
import { Recorder } from './recorder';
import { commands, env, Uri, window } from 'vscode';
import { DisposableObject } from '../pure/disposable-object';
import { getMockGitHubApiServerScenariosPath } from '../config';
/**
* Enables mocking of the GitHub API server via HTTP interception, using msw.
*/
export class MockGitHubApiServer {
export class MockGitHubApiServer extends DisposableObject {
private isListening: boolean;
private config: MockGitHubApiConfigListener;
private readonly server: SetupServerApi;
private readonly recorder: Recorder;
constructor() {
super();
this.isListening = false;
this.config = new MockGitHubApiConfigListener();
this.server = setupServer();
this.recorder = this.push(new Recorder(this.server));
this.setupConfigListener();
}
public startServer(): void {
this.isListening = true;
if (this.isListening) {
return;
}
// TODO: Enable HTTP interception.
this.server.listen();
this.isListening = true;
}
public stopServer(): void {
this.server.close();
this.isListening = false;
// TODO: Disable HTTP interception.
}
public loadScenario(): void {
@@ -33,8 +49,98 @@ export class MockGitHubApiServer {
// TODO: Implement logic to list all available scenarios.
}
public recordScenario(): void {
// TODO: Implement logic to record a new scenario to a directory.
public async recordScenario(): Promise<void> {
if (this.recorder.isRecording) {
void window.showErrorMessage('A scenario is already being recorded. Use the "Save Scenario" or "Cancel Scenario" commands to finish recording.');
return;
}
this.recorder.start();
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', true);
await window.showInformationMessage('Recording scenario. To save the scenario, use the "CodeQL Mock GitHub API Server: Save Scenario" command.');
}
public async saveScenario(): Promise<void> {
const scenariosDirectory = await this.getScenariosDirectory();
if (!scenariosDirectory) {
return;
}
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', false);
if (!this.recorder.isRecording) {
void window.showErrorMessage('No scenario is currently being recorded.');
return;
}
if (this.recorder.scenarioRequestCount === 0) {
void window.showWarningMessage('No requests were recorded. Cancelling scenario.');
await this.stopRecording();
return;
}
const name = await window.showInputBox({
title: 'Save scenario',
prompt: 'Enter a name for the scenario.',
placeHolder: 'successful-run',
});
if (!name) {
return;
}
const filepath = await this.recorder.save(scenariosDirectory, name);
await this.stopRecording();
const action = await window.showInformationMessage(`Scenario saved to ${filepath}`, 'Open directory');
if (action === 'Open directory') {
await env.openExternal(Uri.file(filepath));
}
}
public async cancelRecording(): Promise<void> {
if (!this.recorder.isRecording) {
void window.showErrorMessage('No scenario is currently being recorded.');
return;
}
await this.stopRecording();
void window.showInformationMessage('Recording cancelled.');
}
private async stopRecording(): Promise<void> {
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', false);
await this.recorder.stop();
await this.recorder.clear();
}
private async getScenariosDirectory(): Promise<string | undefined> {
const scenariosDirectory = getMockGitHubApiServerScenariosPath();
if (scenariosDirectory) {
return scenariosDirectory;
}
const directories = await window.showOpenDialog({
canSelectFolders: true,
canSelectFiles: false,
canSelectMany: false,
openLabel: 'Select scenarios directory',
title: 'Select scenarios directory',
});
if (directories === undefined || directories.length === 0) {
void window.showErrorMessage('No scenarios directory selected.');
return undefined;
}
// Unfortunately, we cannot save the directory in the configuration because that requires
// the configuration to be registered. If we do that, it would be visible to all users; there
// is no "when" clause that would allow us to only show it to users who have enabled the feature flag.
return directories[0].fsPath;
}
private setupConfigListener(): void {