dew/packages/mcp/test/mcp_test.dart
Chris Hendrickson 0ad1fae213 chore: 1.0 release prep
Metadata:
- Bump dew CLI version 0.0.1 → 1.0.0
- Add repository + issue_tracker URLs to all pubspec.yaml files
- Switch inter-package path deps to versioned deps (^1.0.0)
- Remove publish_to: none from all packages
- Add MIT LICENSE to root and all packages
- Confirm all four pub.dev names available (dew, dew_core, dew_kanban, dew_mcp)

Documentation:
- Add CHANGELOG.md (Keep a Changelog format, full 1.0.0 feature history)
- Overhaul README.md (pitch, pub.dev badge, quick-start, feature sections)
- Add TUI section + full keybinding tables to docs/features/kanban.md
- Add CONTRIBUTING.md (setup, test, lint, branch strategy, command guide)

Tests:
- Add packages/cli/test/cli_test.dart (6 smoke tests)
- Add packages/kanban/test/integration_test.dart (6 TicketStore e2e tests)
- Expand packages/mcp/test/mcp_test.dart (5 tool registration tests)
- Add dew_kanban as dev dependency in packages/mcp/pubspec.yaml
- 57/57 tests passing

Code quality:
- dart format applied across all 23 changed source files
- dart analyze: zero errors, zero warnings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 15:58:54 -04:00

127 lines
3.8 KiB
Dart

import 'package:dew_core/dew_core.dart';
import 'package:dew_kanban/dew_kanban.dart' as kanban;
import 'package:dew_mcp/dew_mcp.dart';
import 'package:test/test.dart';
void main() {
group('McpCommand', () {
test('has correct name and description', () {
final cmd = McpCommand(CommandRegistry());
expect(cmd.name, 'mcp');
expect(cmd.description, isNotEmpty);
});
test('has serve subcommand', () {
final cmd = McpCommand(CommandRegistry());
expect(cmd.subcommands.keys, contains('serve'));
});
test('registerCommands adds mcp command to registry', () {
final registry = CommandRegistry();
registerCommands(registry);
expect(registry.commands.map((c) => c.name), contains('mcp'));
});
});
group('CommandRegistry.mcpTools', () {
test('starts empty with no feature packages registered', () {
expect(CommandRegistry().mcpTools, isEmpty);
});
test('collects tools from DewToolCommand subcommands', () {
final registry = CommandRegistry();
registry.register(_StubParentCommand());
expect(registry.mcpTools, hasLength(1));
expect(registry.mcpTools.first.name, 'stub_tool');
});
});
group('Kanban MCP tools via CommandRegistry', () {
late List<McpTool> tools;
setUp(() {
final registry = CommandRegistry();
kanban.registerCommands(registry);
tools = registry.mcpTools;
});
test('all 15 expected tool names are registered', () {
expect(tools, hasLength(15));
expect(tools.map((t) => t.name).toSet(), {
'kanban_create_ticket',
'kanban_list_tickets',
'kanban_board',
'kanban_get_ticket',
'kanban_update_ticket',
'kanban_delete_ticket',
'kanban_archive_ticket',
'kanban_unarchive_ticket',
'kanban_move_ticket',
'kanban_search_tickets',
'kanban_add_comment',
'kanban_get_config',
'kanban_stats',
'kanban_link_tickets',
'kanban_unlink_tickets',
});
});
test('kanban_create_ticket schema requires title and type', () {
final create = tools.firstWhere((t) => t.name == 'kanban_create_ticket');
final required = create.inputSchema['required'] as List;
expect(required, containsAll(['title', 'type']));
});
test('kanban_add_comment schema requires id and comment', () {
final comment = tools.firstWhere((t) => t.name == 'kanban_add_comment');
final required = comment.inputSchema['required'] as List;
expect(required, containsAll(['id', 'comment']));
});
test('kanban_search_tickets schema requires query', () {
final search = tools.firstWhere((t) => t.name == 'kanban_search_tickets');
final required = search.inputSchema['required'] as List;
expect(required, contains('query'));
});
test('all tool names follow the snake_case kanban_ prefix pattern', () {
final pattern = RegExp(r'^kanban_[a-z]+(_[a-z]+)*$');
for (final tool in tools) {
expect(
pattern.hasMatch(tool.name),
isTrue,
reason: '${tool.name} does not match snake_case kanban_ pattern',
);
}
});
});
}
class _StubToolCommand extends DewCommand with DewToolCommand {
_StubToolCommand() {
argParser.addOption('input', mandatory: true, help: 'Some input.');
}
@override
final String name = 'stub';
@override
final String description = 'A stub tool command.';
@override
final String toolName = 'stub_tool';
@override
Future<String> callAsTool(Map<String, dynamic> args) async => 'ok';
}
class _StubParentCommand extends DewCommand {
_StubParentCommand() {
addSubcommand(_StubToolCommand());
}
@override
final String name = 'parent';
@override
final String description = 'Parent.';
@override
Future<void> run() async => printUsage();
}