blob: 2392d30da6759758b91c455672df554937d85c82 [file] [log] [blame]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:dart_mcp/server.dart';
import 'package:dart_mcp_server/src/utils/cli_utils.dart';
import 'package:dart_mcp_server/src/utils/constants.dart';
import 'package:dart_mcp_server/src/utils/sdk.dart';
import 'package:file/memory.dart';
import 'package:process/process.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';
import '../test_harness.dart';
void main() {
late MemoryFileSystem fileSystem;
setUp(() {
fileSystem = MemoryFileSystem();
});
group('can run commands', () {
late TestProcessManager processManager;
setUp(() async {
processManager = TestProcessManager();
});
test('can run commands with exact matches for roots', () async {
final result = await runCommandInRoots(
CallToolRequest(
name: 'foo',
arguments: {
ParameterNames.roots: [
{ParameterNames.root: 'file:///bar/'},
],
},
),
commandForRoot: (_, _, _) => 'testCommand',
arguments: ['a', 'b'],
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///bar/')],
fileSystem: fileSystem,
sdk: Sdk(),
);
expect(result.isError, isNot(true));
expect(processManager.commandsRan, [
equalsCommand((
command: ['testCommand', 'a', 'b'],
workingDirectory: '/bar/',
)),
]);
});
test(
'can run commands with roots that are subdirectories of known roots',
() async {
final result = await runCommandInRoots(
CallToolRequest(
name: 'foo',
arguments: {
ParameterNames.roots: [
{ParameterNames.root: 'file:///bar/baz/'},
],
},
),
commandForRoot: (_, _, _) => 'testCommand',
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///bar/')],
fileSystem: fileSystem,
sdk: Sdk(),
);
expect(result.isError, isNot(true));
expect(processManager.commandsRan, [
equalsCommand((
command: ['testCommand'],
workingDirectory: '/bar/baz/',
)),
]);
},
);
});
group('cannot run commands', () {
final processManager = FakeProcessManager();
test('with roots outside of known roots', () async {
for (var invalidRoot in ['file:///bar/', 'file:///foo/../bar/']) {
final result = await runCommandInRoots(
CallToolRequest(
name: 'foo',
arguments: {
ParameterNames.roots: [
{ParameterNames.root: invalidRoot},
],
},
),
commandForRoot: (_, _, _) => 'fake',
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///foo/')],
fileSystem: fileSystem,
sdk: Sdk(),
);
expect(result.isError, isTrue);
expect(
result.content.single,
isA<TextContent>().having(
(t) => t.text,
'text',
allOf(contains('Invalid root $invalidRoot')),
),
);
}
});
test('with paths outside of known roots', () async {
final result = await runCommandInRoots(
CallToolRequest(
name: 'foo',
arguments: {
ParameterNames.roots: [
{
ParameterNames.root: 'file:///foo/',
ParameterNames.paths: [
'file:///bar/',
'../baz/',
'zip/../../zap/',
'ok.dart',
],
},
],
},
),
commandForRoot: (_, _, _) => 'fake',
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///foo/')],
fileSystem: fileSystem,
sdk: Sdk(),
);
expect(result.isError, isTrue);
expect(
result.content.single,
isA<TextContent>().having(
(t) => t.text,
'text',
allOf(
contains('Paths are not allowed to escape their project root'),
contains('bar/'),
contains('baz/'),
contains('zap/'),
isNot(contains('ok.dart')),
),
),
);
});
});
}
class FakeProcessManager extends Fake implements ProcessManager {}