| @TestOn('chrome') |
| library; |
| |
| import 'package:dartpad/dartpad.dart'; |
| import 'package:dartpad_editor/dartpad_editor.dart'; |
| import 'package:frontend/features/agent/logic/coordination/agent_diagnostics_coordinator.dart'; |
| import 'package:frontend/features/agent/logic/coordination/agent_pub_get_coordinator.dart'; |
| import 'package:frontend/features/agent/tools/tool_schemas.dart'; |
| import 'package:frontend/features/agent/tools/workspace/analyze_start_tool.dart'; |
| import 'package:frontend/features/workspace/data/workspace_repository.dart'; |
| import 'package:mocktail/mocktail.dart'; |
| import 'package:test/test.dart'; |
| |
| class MockWorkspaceResourceApi extends Mock implements WorkspaceResourceApi {} |
| |
| class MockWorkspaceRepository extends Mock implements WorkspaceRepository {} |
| |
| class MockLanguageServerClient extends Mock implements LanguageServerClient {} |
| |
| class TimeoutDiagnosticsCoordinator extends AgentDiagnosticsCoordinator { |
| TimeoutDiagnosticsCoordinator({ |
| required super.workspaceRepository, |
| }); |
| |
| @override |
| int get mutationGeneration => 3; |
| |
| @override |
| Future<AgentDiagnosticsSnapshot> readFreshSnapshot({ |
| List<String>? paths, |
| }) async { |
| throw const AgentDiagnosticsFreshnessException( |
| 'No current analyzer diagnostics were published for workspace mutation generation 3 within 3000 ms.', |
| mutationGeneration: 3, |
| ); |
| } |
| } |
| |
| void main() { |
| group('AnalyzeStartTool', () { |
| late MockWorkspaceRepository mockRepo; |
| late MockLanguageServerClient mockLsp; |
| late AnalyzeStartTool tool; |
| |
| setUp(() { |
| mockRepo = MockWorkspaceRepository(); |
| mockLsp = MockLanguageServerClient(); |
| when(() => mockRepo.languageServerClient).thenReturn(mockLsp); |
| tool = AnalyzeStartTool( |
| workspaceRepository: mockRepo, |
| pubGetCoordinator: AgentPubGetCoordinator(workspaceRepository: mockRepo), |
| ); |
| }); |
| |
| test('returns success with empty diagnostics for clean code', () async { |
| when(() => mockLsp.allDiagnostics).thenReturn(const []); |
| |
| final output = await tool.run(AnalyzeStartInput(paths: null)); |
| |
| expect(output.success, isTrue); |
| expect(output.diagnostics, isEmpty); |
| expect(output.summary, equals('analyzeStart - passed')); |
| expect(output.error, isNull); |
| }); |
| |
| test('returns failure and structured diagnostics for analyzer errors', () async { |
| when(() => mockLsp.allDiagnostics).thenReturn([ |
| const DiagnosticEntry( |
| 'lib/main.dart', |
| Diagnostic( |
| line: 10, |
| character: 5, |
| message: 'Undefined name.', |
| severity: DiagnosticSeverity.error, |
| ), |
| ), |
| ]); |
| |
| final output = await tool.run(AnalyzeStartInput(paths: null)); |
| |
| expect(output.success, isFalse); |
| expect(output.diagnostics, hasLength(1)); |
| |
| final diag = output.diagnostics!.first; |
| expect(diag.path, equals('lib/main.dart')); |
| expect(diag.line, equals(11)); |
| expect(diag.character, equals(6)); |
| expect(diag.severity, equals('error')); |
| expect(diag.message, equals('Undefined name.')); |
| expect(output.summary, equals('analyzeStart - found 1 diagnostics')); |
| }); |
| |
| test('returns failure for warnings-only diagnostics', () async { |
| when(() => mockLsp.allDiagnostics).thenReturn([ |
| const DiagnosticEntry( |
| 'lib/main.dart', |
| Diagnostic( |
| line: 0, |
| character: 0, |
| message: 'Unused import.', |
| severity: DiagnosticSeverity.warning, |
| ), |
| ), |
| ]); |
| |
| final output = await tool.run(AnalyzeStartInput(paths: null)); |
| final event = tool.mapToToolEvent(AnalyzeStartInput(paths: null), output); |
| |
| expect(output.success, isFalse); |
| expect(output.diagnostics, hasLength(1)); |
| expect(output.summary, contains('analyzeStart - found 1 diagnostics')); |
| expect(event.success, isFalse); |
| expect(event.summary, contains('analyzeStart - found 1 diagnostics')); |
| }); |
| |
| test('reports freshness timeout as blocking and not clean', () async { |
| tool = AnalyzeStartTool( |
| workspaceRepository: mockRepo, |
| pubGetCoordinator: AgentPubGetCoordinator(workspaceRepository: mockRepo), |
| diagnosticsCoordinator: TimeoutDiagnosticsCoordinator(workspaceRepository: mockRepo), |
| ); |
| |
| final output = await tool.run(AnalyzeStartInput(paths: null)); |
| final event = tool.mapToToolEvent(AnalyzeStartInput(paths: null), output); |
| |
| expect(output.success, isFalse); |
| expect(output.summary, equals('analyzeStart - blocked by analyzer freshness timeout')); |
| expect(output.error, contains('not a clean analyzer result')); |
| expect(output.error, contains('Do not treat this as a pass')); |
| expect(event.success, isFalse); |
| expect(event.summary, equals('analyzeStart - blocked by analyzer freshness timeout')); |
| expect(event.errorMessage, contains('not a clean analyzer result')); |
| }); |
| |
| test('blocks repeated freshness timeout for the same mutation generation without waiting again', () async { |
| tool = AnalyzeStartTool( |
| workspaceRepository: mockRepo, |
| pubGetCoordinator: AgentPubGetCoordinator(workspaceRepository: mockRepo), |
| diagnosticsCoordinator: TimeoutDiagnosticsCoordinator(workspaceRepository: mockRepo), |
| ); |
| |
| final first = await tool.run(AnalyzeStartInput(paths: null)); |
| final second = await tool.run(AnalyzeStartInput(paths: null)); |
| |
| expect(first.summary, equals('analyzeStart - blocked by analyzer freshness timeout')); |
| expect(second.success, isFalse); |
| expect(second.summary, equals('analyzeStart - repeated analyzer freshness timeout')); |
| expect(second.error, contains('Do not call analyzeStart again for this same generation')); |
| expect(second.error, contains('analyzer/LSP infrastructure issue')); |
| }); |
| |
| test('waits for dependency resolution before analyzer validation', () async { |
| final mockWorkspaceResourceApi = MockWorkspaceResourceApi(); |
| when(() => mockWorkspaceResourceApi.fileExist('pubspec.yaml')).thenAnswer((_) async => true); |
| when( |
| () => mockRepo.runPub( |
| command: 'get', |
| ), |
| ).thenAnswer((_) async => (log: 'Resolved dependencies successfully!')); |
| when(() => mockRepo.root).thenReturn(WorkspaceFolder(workspace: mockWorkspaceResourceApi, path: '')); |
| when(() => mockRepo.workspaceResourceApi).thenReturn(mockWorkspaceResourceApi); |
| when(() => mockLsp.allDiagnostics).thenReturn(const []); |
| |
| final manager = AgentPubGetCoordinator(workspaceRepository: mockRepo)..markPubspecChanged(); |
| tool = AnalyzeStartTool( |
| workspaceRepository: mockRepo, |
| pubGetCoordinator: manager, |
| ); |
| |
| final output = await tool.run(AnalyzeStartInput(paths: null)); |
| final event = tool.mapToToolEvent(AnalyzeStartInput(paths: null), output); |
| |
| expect(output.success, isTrue); |
| expect(output.error, isNull); |
| expect(event.summary, equals('analyzeStart - passed')); |
| verify(() => mockRepo.runPub(command: 'get')).called(1); |
| }); |
| }); |
| } |