Skip to content

Commit 18965a2

Browse files
Address comments
1 parent f9f8627 commit 18965a2

4 files changed

Lines changed: 103 additions & 1 deletion

File tree

src/extension/debugger/configuration/providers/fastapiLaunch.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export async function buildFastAPILaunchDebugConfiguration(
3535
const autoArgs = state.folder ? tryResolveFastApiArgs(state.folder, fastApiPaths) : undefined;
3636

3737
let args: string[];
38+
let manuallyEnteredAValue = false;
3839
if (autoArgs) {
3940
args = autoArgs;
4041
} else {
@@ -46,6 +47,7 @@ export async function buildFastAPILaunchDebugConfiguration(
4647
return;
4748
}
4849
args = ['run', entered];
50+
manuallyEnteredAValue = true;
4951
}
5052

5153
const config: Partial<LaunchRequestArguments> = {
@@ -58,6 +60,8 @@ export async function buildFastAPILaunchDebugConfiguration(
5860
};
5961
sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, {
6062
configurationType: DebugConfigurationType.launchFastAPI,
63+
autoDetectedFastAPIMainPyPath: !!autoArgs,
64+
manuallyEnteredAValue,
6165
});
6266
Object.assign(state.config, config);
6367
}

src/extension/debugger/configuration/utils/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export async function getFastApiPaths(folder: WorkspaceFolder | undefined) {
8282
return fastApiPaths;
8383
}
8484

85-
export function tryResolveFastApiArgs(folder: WorkspaceFolder, paths: Uri[]): string[] | undefined {
85+
export function tryResolveFastApiArgs(folder: WorkspaceFolder, paths: readonly Uri[]): string[] | undefined {
8686
if (paths.length !== 1) {
8787
return undefined;
8888
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { expect } from 'chai';
7+
import * as path from 'path';
8+
import * as sinon from 'sinon';
9+
import { Uri, WorkspaceFolder } from 'vscode';
10+
import { DynamicPythonDebugConfigurationService } from '../../../extension/debugger/configuration/dynamicdebugConfigurationService';
11+
import * as configurationUtils from '../../../extension/debugger/configuration/utils/configuration';
12+
13+
suite('Debugging - Dynamic Debug Configuration Service', () => {
14+
const folder: WorkspaceFolder = { uri: Uri.file('/work'), name: 'ws', index: 0 };
15+
let service: DynamicPythonDebugConfigurationService;
16+
let getFastApiPathsStub: sinon.SinonStub;
17+
18+
setup(() => {
19+
service = new DynamicPythonDebugConfigurationService();
20+
sinon.stub(configurationUtils, 'getDjangoPaths').resolves([]);
21+
sinon.stub(configurationUtils, 'getFlaskPaths').resolves([]);
22+
getFastApiPathsStub = sinon.stub(configurationUtils, 'getFastApiPaths');
23+
});
24+
25+
teardown(() => {
26+
sinon.restore();
27+
});
28+
29+
const fastApiProviders = async () => {
30+
const result = await service.provideDebugConfigurations(folder);
31+
return (result ?? []).filter((c) => c.name?.includes('FastAPI'));
32+
};
33+
34+
test('No FastAPI detected → no FastAPI configs offered', async () => {
35+
getFastApiPathsStub.resolves([]);
36+
37+
const fastApi = await fastApiProviders();
38+
expect(fastApi).to.have.length(0);
39+
});
40+
41+
test('Single match at workspace root → project config uses resolved path, file variant uses ${file}', async () => {
42+
getFastApiPathsStub.resolves([Uri.file('/work/main.py')]);
43+
44+
const fastApi = await fastApiProviders();
45+
expect(fastApi).to.have.length(2);
46+
expect(fastApi[0]).to.include({ name: 'Python Debugger: FastAPI', module: 'fastapi' });
47+
expect(fastApi[0].args).to.deep.equal(['run', 'main.py']);
48+
expect(fastApi[1]).to.include({ name: 'Python Debugger: FastAPI File', module: 'fastapi' });
49+
expect(fastApi[1].args).to.deep.equal(['run', '${file}']);
50+
});
51+
52+
test('Single match in subdirectory → project config passes path explicitly', async () => {
53+
getFastApiPathsStub.resolves([Uri.file('/work/backend/app/main.py')]);
54+
55+
const fastApi = await fastApiProviders();
56+
expect(fastApi[0].args).to.deep.equal(['run', path.join('backend', 'app', 'main.py')]);
57+
});
58+
59+
test('Multiple matches → project config falls back to plain `fastapi run`', async () => {
60+
getFastApiPathsStub.resolves([Uri.file('/work/svc-a/main.py'), Uri.file('/work/svc-b/main.py')]);
61+
62+
const fastApi = await fastApiProviders();
63+
expect(fastApi[0].args).to.deep.equal(['run']);
64+
});
65+
});

src/test/unittest/configuration/providers/fastapiLaunch.unit.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,36 @@ suite('Debugging - Configuration Provider FastAPI', () => {
138138
expect(state.config).to.be.deep.equal(config);
139139
});
140140
});
141+
142+
suite('Debugging - tryResolveFastApiArgs', () => {
143+
const workspaceFolder = (root: string) => ({
144+
uri: Uri.file(root),
145+
name: 'ws',
146+
index: 0,
147+
});
148+
149+
test('Returns undefined for empty paths', () => {
150+
expect(configurationUtils.tryResolveFastApiArgs(workspaceFolder('/work'), [])).to.equal(undefined);
151+
});
152+
153+
test('Returns undefined for multiple paths', () => {
154+
const paths = [Uri.file('/work/svc-a/main.py'), Uri.file('/work/svc-b/main.py')];
155+
expect(configurationUtils.tryResolveFastApiArgs(workspaceFolder('/work'), paths)).to.equal(undefined);
156+
});
157+
158+
test('Returns resolved path for single root-level match', () => {
159+
const paths = [Uri.file('/work/main.py')];
160+
expect(configurationUtils.tryResolveFastApiArgs(workspaceFolder('/work'), paths)).to.deep.equal([
161+
'run',
162+
'main.py',
163+
]);
164+
});
165+
166+
test('Returns resolved path for single nested match', () => {
167+
const paths = [Uri.file('/work/backend/app/main.py')];
168+
expect(configurationUtils.tryResolveFastApiArgs(workspaceFolder('/work'), paths)).to.deep.equal([
169+
'run',
170+
path.join('backend', 'app', 'main.py'),
171+
]);
172+
});
173+
});

0 commit comments

Comments
 (0)