blob: ac6f83e334caa57048c977bb7c04192236db49de [file]
@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);
});
});
}