pipe/test/verify_removed_test.dart
trvon 4f3f22acad
Some checks failed
Lint Commits / commitlint (pull_request) Successful in 15s
Tests / Static Analysis (pull_request) Has been cancelled
Tests / Tests (pull_request) Has been cancelled
feat(verify): report a deleted subject as removed, not resolved
Found by dogfooding on nuwayze main: `pipe verify` against the ratchet
baseline printed

  resolved  1 no longer reported - these fixes are confirmed
    + service_browse_bloc.dart:20  LoadServicesByArtist  [dead-handler]

but nothing was fixed. nuwayze commit ccca9bd1e ("delete dead firestore
query code") deleted the event and its handler. Under pipe's default
wire-up policy, deleting an unreachable handler is the exception that
needs approval, so calling it a confirmed fix overstated what happened.

verify now separates the two. A gone finding is REMOVED when its
subject no longer exists:
- its file is not among the project's files;
- for rules labelled by a declaration (the event/state/union/provider or
  owner class), that name is no longer declared anywhere;
- for dead-handler, the on<Event> registration itself is gone.
Everything else stays RESOLVED. Member-labelled rules (a method or
build) are classed by file only, never guessed. Removed entries still
count as gone for --expect-resolved; they are only named honestly, in
text ("removed   N gone because the code was deleted - not wired up",
a REMOVED section) and JSON (removed, removedEntries).

On nuwayze main the entry now reads REMOVED. Tests: a unit test over
hand-built entries (a wired handler stays resolved, a deleted handler
and a deleted file are removed, a member label is not guessed), and an
end-to-end test that deletes a fixture's dead handler and runs
verifyAnalysis. 216 tests pass.
2026-09-24 20:04:52 -06:00

69 lines
2.1 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:pipe/pipe.dart';
import 'package:test/test.dart';
/// A copy of the fixture, so deleting code there cannot touch the real one.
String _fixtureCopy() {
final from = Directory('test/fixtures/stuck_loading');
final to = Directory.systemTemp.createTempSync('pipe-verify-removed');
for (final entity in from.listSync(recursive: true)) {
final relative = entity.path.substring(from.path.length + 1);
final target = '${to.path}/$relative';
if (entity is Directory) {
Directory(target).createSync(recursive: true);
} else if (entity is File) {
File(target)
..createSync(recursive: true)
..writeAsBytesSync(entity.readAsBytesSync());
}
}
return to.path;
}
void main() {
test('verify reports a deleted dead handler as removed', () async {
final root = _fixtureCopy();
final before = await analyzeProject(
await loadProject(root),
concurrency: 1,
);
final baseline = decodeBaselineEntries(
encodeBaseline(root, before.report.findings),
);
await before.dispose();
expect(
baseline.where(
(e) => e.rule == ruleDeadHandler && e.label == 'NeverDispatched',
),
hasLength(1),
);
// Delete the dead handler instead of wiring it up.
final gaps = File('$root/lib/gaps.dart');
gaps.writeAsStringSync(
gaps.readAsStringSync().replaceFirst(
'on<NeverDispatched>((event, emit) => emit(const GapIdle()));',
'',
),
);
final after = await analyzeProject(await loadProject(root), concurrency: 1);
try {
final verification = verifyAnalysis(analysis: after, baseline: baseline);
final json = jsonDecode(jsonEncode(verification.toJson())) as Map;
expect(
verification.removed.map((e) => e.label),
contains('NeverDispatched'),
);
expect(
verification.resolved.map((e) => e.label),
isNot(contains('NeverDispatched')),
);
expect(json['removed'], greaterThanOrEqualTo(1));
} finally {
await after.dispose();
}
});
}