import 'package:flutter_test/flutter_test.dart'; import 'package:life_helper/ai/tools/tool_definition.dart'; import 'package:life_helper/ai/tools/tool_dispatcher.dart'; import 'package:life_helper/ai/tools/tool_envelope.dart'; import 'package:life_helper/ai/tools/tool_registry.dart'; import '_tool_test_helpers.dart'; void main() { group('ToolDispatcher', () { test('unknown tool 이름', () async { final ctx = await bootstrapToolDeps(); addTearDown(() => ctx.db.close()); final dispatcher = ToolDispatcher(registry: ToolRegistry.defaults()); final r = await dispatcher.dispatch( toolName: 'no_such', rawArgs: const {}, confirmContext: null, deps: ctx.deps, ); expect(r, isA()); expect((r as ToolErr).code, 'unknown_tool'); }); test('validation: required 없음', () async { final ctx = await bootstrapToolDeps(); addTearDown(() => ctx.db.close()); final dispatcher = ToolDispatcher(registry: ToolRegistry.defaults()); final r = await dispatcher.dispatch( toolName: 'query_protocol', rawArgs: const {}, confirmContext: null, deps: ctx.deps, ); expect(r, isA()); expect((r as ToolErr).code, 'validation'); }); test('validation: 타입 불일치', () async { final ctx = await bootstrapToolDeps(); addTearDown(() => ctx.db.close()); final dispatcher = ToolDispatcher(registry: ToolRegistry.defaults()); final r = await dispatcher.dispatch( toolName: 'query_protocol', rawArgs: const {'id': 123}, confirmContext: null, deps: ctx.deps, ); expect(r, isA()); expect((r as ToolErr).code, 'validation'); }); test('destructive + null confirmContext → ToolCancelled', () async { final ctx = await bootstrapToolDeps(); addTearDown(() => ctx.db.close()); final dispatcher = ToolDispatcher(registry: ToolRegistry.defaults()); final r = await dispatcher.dispatch( toolName: 'add_habit', rawArgs: const { 'protocol_id': 'morning_sunlight', 'frame_level': 'L2', 'framed_text': '햇빛', }, confirmContext: null, deps: ctx.deps, ); expect(r, isA()); }); test('read-only normal 경로', () async { final ctx = await bootstrapToolDeps(); addTearDown(() => ctx.db.close()); final dispatcher = ToolDispatcher(registry: ToolRegistry.defaults()); final r = await dispatcher.dispatch( toolName: 'search_catalog', rawArgs: const {}, confirmContext: null, deps: ctx.deps, ); expect(r, isA()); }); test('handler 예외 → ToolErr(handler_error)', () async { final ctx = await bootstrapToolDeps(); addTearDown(() => ctx.db.close()); final throwing = ToolDefinition( name: 'always_throws', description: 'test', parametersSchema: const { 'type': 'object', 'properties': {}, 'required': [], }, handler: (_, _) async => throw StateError('boom'), ); final dispatcher = ToolDispatcher( registry: ToolRegistry([throwing]), ); final r = await dispatcher.dispatch( toolName: 'always_throws', rawArgs: const {}, confirmContext: null, deps: ctx.deps, ); expect(r, isA()); expect((r as ToolErr).code, 'handler_error'); }); }); }