cyberhybridhub/scripts/check_admin_portal_coverage.dart
2026-05-31 11:17:12 -05:00

132 lines
3.4 KiB
Dart

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<String> failures = <String>[];
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<String> 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<String> 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;
}