import 'dart:io'; /// Enforces FLUTTER-TDD-PLAN.md Section 2 line-coverage targets for admin portal files. void main() { final String root = _repoRoot(); final List<_CoverageGate> gates = <_CoverageGate>[ _CoverageGate( label: 'server admin logic', lcovPath: '$root/server/coverage/lcov.info', pathContains: 'market_history_admin_logic.dart', minLinePercent: 95, ), _CoverageGate( label: 'server admin handler', lcovPath: '$root/server/coverage/lcov.info', pathContains: 'market_history_admin_handler.dart', minLinePercent: 85, ), _CoverageGate( label: 'server admin actions', lcovPath: '$root/server/coverage/lcov.info', pathContains: 'market_history_admin_actions.dart', minLinePercent: 85, ), _CoverageGate( label: 'flutter admin models', lcovPath: '$root/coverage/lcov.info', pathContains: 'lib/admin/models/', minLinePercent: 90, ), _CoverageGate( label: 'flutter admin repositories', lcovPath: '$root/coverage/lcov.info', pathContains: 'lib/admin/repositories/', minLinePercent: 90, ), _CoverageGate( label: 'flutter admin widgets', lcovPath: '$root/coverage/lcov.info', pathContains: 'lib/admin/widgets/', minLinePercent: 75, ), _CoverageGate( label: 'flutter admin screens', lcovPath: '$root/coverage/lcov.info', pathContains: 'lib/admin/screens/', minLinePercent: 75, ), ]; final List failures = []; for (final _CoverageGate gate in gates) { final File file = File(gate.lcovPath); if (!file.existsSync()) { failures.add('${gate.label}: missing ${gate.lcovPath}'); continue; } final double pct = _lineCoveragePercent( file.readAsLinesSync(), gate.pathContains, ); stdout.writeln( '${gate.label}: ${pct.toStringAsFixed(1)}% (min ${gate.minLinePercent}%)', ); if (pct + 0.05 < gate.minLinePercent) { failures.add( '${gate.label}: ${pct.toStringAsFixed(1)}% < ${gate.minLinePercent}%', ); } } if (failures.isNotEmpty) { stderr.writeln('Admin portal coverage gates failed:'); for (final String failure in failures) { stderr.writeln(' - $failure'); } exit(1); } } String _repoRoot() { final String script = Platform.script.toFilePath(); return File(script).parent.parent.path; } double _lineCoveragePercent(List lines, String pathContains) { double total = 0; double hit = 0; String? currentFile; for (final String line in lines) { if (line.startsWith('SF:')) { currentFile = line.substring(3); continue; } if (line == 'end_of_record') { currentFile = null; continue; } if (currentFile == null || !currentFile.contains(pathContains)) { continue; } if (line.startsWith('DA:')) { final List parts = line.substring(3).split(','); if (parts.length != 2) { continue; } total++; if (int.tryParse(parts[1]) != 0) { hit++; } } } if (total == 0) { return 0; } return hit * 100 / total; } class _CoverageGate { const _CoverageGate({ required this.label, required this.lcovPath, required this.pathContains, required this.minLinePercent, }); final String label; final String lcovPath; final String pathContains; final int minLinePercent; }