Skip to content

Commit 56b4243

Browse files
committed
Improve scenario recording
1 parent ae2d6ce commit 56b4243

File tree

4 files changed

+52
-65
lines changed

4 files changed

+52
-65
lines changed

extensions/ql-vscode/src/common/mock-gh-api/gh-api-request.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,28 @@ export interface GetVariantAnalysisRepoResultRequest {
7474
};
7575
}
7676

77+
interface CodeSearchResponse {
78+
total_count: number;
79+
items: Array<{
80+
repository: Repository;
81+
}>;
82+
}
83+
7784
interface CodeSearchRequest {
7885
request: {
7986
kind: RequestKind.CodeSearch;
8087
query: string;
8188
};
8289
response: {
8390
status: number;
84-
body?: {
85-
total_count?: number;
86-
items?: Array<{
87-
repository: Repository;
88-
}>;
89-
};
90-
message?: string;
91+
body?: CodeSearchResponse | BasicErorResponse;
9192
};
9293
}
9394

95+
interface AutoModelResponse {
96+
models: string;
97+
}
98+
9499
interface AutoModelRequest {
95100
request: {
96101
kind: RequestKind.AutoModel;
@@ -100,10 +105,7 @@ interface AutoModelRequest {
100105
};
101106
response: {
102107
status: number;
103-
body?: {
104-
models: string;
105-
};
106-
message?: string;
108+
body?: AutoModelResponse | BasicErorResponse;
107109
};
108110
}
109111

extensions/ql-vscode/src/common/mock-gh-api/mock-gh-api-server.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,31 @@ import { getDirectoryNamesInsidePath } from "../files";
1212
* Enables mocking of the GitHub API server via HTTP interception, using msw.
1313
*/
1414
export class MockGitHubApiServer extends DisposableObject {
15+
private _isListening: boolean;
16+
1517
private readonly server: SetupServer;
1618
private readonly recorder: Recorder;
1719

1820
constructor() {
1921
super();
22+
this._isListening = false;
2023

2124
this.server = setupServer();
2225
this.recorder = this.push(new Recorder(this.server));
2326
}
2427

28+
public startServer(): void {
29+
if (this._isListening) {
30+
return;
31+
}
32+
33+
this.server.listen({ onUnhandledRequest: "bypass" });
34+
this._isListening = true;
35+
}
36+
2537
public stopServer(): void {
2638
this.server.close();
39+
this._isListening = false;
2740
}
2841

2942
public async loadScenario(
@@ -42,7 +55,6 @@ export class MockGitHubApiServer extends DisposableObject {
4255
const handlers = await createRequestHandlers(scenarioPath);
4356
this.server.resetHandlers();
4457
this.server.use(...handlers);
45-
this.server.listen({ onUnhandledRequest: "bypass" });
4658
}
4759

4860
public async saveScenario(
@@ -99,6 +111,10 @@ export class MockGitHubApiServer extends DisposableObject {
99111
return await getDirectoryNamesInsidePath(scenariosPath);
100112
}
101113

114+
public get isListening(): boolean {
115+
return this._isListening;
116+
}
117+
102118
public get isRecording(): boolean {
103119
return this.recorder.isRecording;
104120
}

extensions/ql-vscode/src/common/mock-gh-api/recorder.ts

+15-52
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { join } from "path";
33

44
import { SetupServer } from "msw/node";
55

6-
import fetch from "node-fetch";
7-
86
import { DisposableObject } from "../disposable-object";
97

108
import {
@@ -14,14 +12,12 @@ import {
1412
} from "./gh-api-request";
1513

1614
export class Recorder extends DisposableObject {
17-
private readonly allRequests = new Map<string, Request>();
1815
private currentRecordedScenario: GitHubApiRequest[] = [];
1916

2017
private _isRecording = false;
2118

2219
constructor(private readonly server: SetupServer) {
2320
super();
24-
this.onRequestStart = this.onRequestStart.bind(this);
2521
this.onResponseBypass = this.onResponseBypass.bind(this);
2622
}
2723

@@ -42,7 +38,6 @@ export class Recorder extends DisposableObject {
4238

4339
this.clear();
4440

45-
this.server.events.on("request:start", this.onRequestStart);
4641
this.server.events.on("response:bypass", this.onResponseBypass);
4742
}
4843

@@ -53,13 +48,11 @@ export class Recorder extends DisposableObject {
5348

5449
this._isRecording = false;
5550

56-
this.server.events.removeListener("request:start", this.onRequestStart);
5751
this.server.events.removeListener("response:bypass", this.onResponseBypass);
5852
}
5953

6054
public clear() {
6155
this.currentRecordedScenario = [];
62-
this.allRequests.clear();
6356
}
6457

6558
public async save(scenariosPath: string, name: string): Promise<string> {
@@ -109,34 +102,14 @@ export class Recorder extends DisposableObject {
109102
return scenarioDirectory;
110103
}
111104

112-
private onRequestStart(request: Request, requestId: string): void {
113-
if (request.headers.has("x-vscode-codeql-msw-bypass")) {
114-
return;
115-
}
116-
117-
this.allRequests.set(requestId, request);
118-
}
119-
120105
private async onResponseBypass(
121106
response: Response,
122-
_: Request,
123-
requestId: string,
107+
request: Request,
108+
_requestId: string,
124109
): Promise<void> {
125-
const request = this.allRequests.get(requestId);
126-
this.allRequests.delete(requestId);
127-
if (!request) {
128-
return;
129-
}
130-
131-
if (response.body === undefined) {
132-
return;
133-
}
134-
135110
const gitHubApiRequest = await createGitHubApiRequest(
136-
request.url.toString(),
137-
response.status,
138-
response.body?.toString() || "",
139-
response.headers,
111+
request.url,
112+
response,
140113
);
141114
if (!gitHubApiRequest) {
142115
return;
@@ -148,22 +121,23 @@ export class Recorder extends DisposableObject {
148121

149122
async function createGitHubApiRequest(
150123
url: string,
151-
status: number,
152-
body: string,
153-
headers: globalThis.Headers,
124+
response: Response,
154125
): Promise<GitHubApiRequest | undefined> {
155126
if (!url) {
156127
return undefined;
157128
}
158129

130+
const status = response.status;
131+
const headers = response.headers;
132+
159133
if (url.match(/\/repos\/[a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_.]+$/)) {
160134
return {
161135
request: {
162136
kind: RequestKind.GetRepo,
163137
},
164138
response: {
165139
status,
166-
body: JSON.parse(body),
140+
body: await response.json(),
167141
},
168142
};
169143
}
@@ -177,7 +151,7 @@ async function createGitHubApiRequest(
177151
},
178152
response: {
179153
status,
180-
body: JSON.parse(body),
154+
body: await response.json(),
181155
},
182156
};
183157
}
@@ -193,7 +167,7 @@ async function createGitHubApiRequest(
193167
},
194168
response: {
195169
status,
196-
body: JSON.parse(body),
170+
body: await response.json(),
197171
},
198172
};
199173
}
@@ -209,7 +183,7 @@ async function createGitHubApiRequest(
209183
},
210184
response: {
211185
status,
212-
body: JSON.parse(body),
186+
body: await response.json(),
213187
},
214188
};
215189
}
@@ -219,25 +193,14 @@ async function createGitHubApiRequest(
219193
/objects-origin\.githubusercontent\.com\/codeql-query-console\/codeql-variant-analysis-repo-tasks\/\d+\/(?<repositoryId>\d+)/,
220194
);
221195
if (repoDownloadMatch?.groups?.repositoryId) {
222-
// msw currently doesn't support binary response bodies, so we need to download this separately
223-
// see https://github.com/mswjs/interceptors/blob/15eafa6215a328219999403e3ff110e71699b016/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts#L24-L33
224-
// Essentially, mws is trying to decode a ZIP file as UTF-8 which changes the bytes and corrupts the file.
225-
const response = await fetch(url, {
226-
headers: {
227-
// We need to ensure we don't end up in an infinite loop, since this request will also be intercepted
228-
"x-vscode-codeql-msw-bypass": "true",
229-
},
230-
});
231-
const responseBuffer = await response.buffer();
232-
233196
return {
234197
request: {
235198
kind: RequestKind.GetVariantAnalysisRepoResult,
236199
repositoryId: parseInt(repoDownloadMatch.groups.repositoryId, 10),
237200
},
238201
response: {
239202
status,
240-
body: responseBuffer,
203+
body: Buffer.from(await response.arrayBuffer()),
241204
contentType: headers.get("content-type") ?? "application/octet-stream",
242205
},
243206
};
@@ -252,7 +215,7 @@ async function createGitHubApiRequest(
252215
},
253216
response: {
254217
status,
255-
body: JSON.parse(body),
218+
body: await response.json(),
256219
},
257220
};
258221
}
@@ -267,7 +230,7 @@ async function createGitHubApiRequest(
267230
},
268231
response: {
269232
status,
270-
body: JSON.parse(body),
233+
body: await response.json(),
271234
},
272235
};
273236
}

extensions/ql-vscode/src/common/mock-gh-api/vscode/vscode-mock-gh-api-server.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
4242
};
4343
}
4444

45+
public async startServer(): Promise<void> {
46+
this.server.startServer();
47+
}
48+
4549
public async stopServer(): Promise<void> {
4650
this.server.stopServer();
4751

@@ -252,7 +256,9 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
252256
}
253257

254258
private async onConfigChange(): Promise<void> {
255-
if (!this.config.mockServerEnabled) {
259+
if (this.config.mockServerEnabled && !this.server.isListening) {
260+
await this.startServer();
261+
} else if (!this.config.mockServerEnabled && this.server.isListening) {
256262
await this.stopServer();
257263
}
258264
}

0 commit comments

Comments
 (0)