pipe/test/state_map_roundtrip_test.dart
trvon 0d891919e9
feat(check): run the rules on a state model written by any front end
Third step of the state-map seam, and the proof that it holds.

stateFlowFromJson rebuilds a StateFlow from its toJson form (computed
members - totals, coverage differences, Riverpod gaps - are recomputed),
and `pipe check --model <file>` runs every rule on such a file instead
of analysing Dart. A front end for another language now only has to
write that JSON to get every rule, --baseline ratcheting and --fail-on
gating. StateUnion.toJson now carries uiReadsBaseFields (only when
true), which the round trip showed it had been dropping.

Checked byte for byte, through a file on disk: `pipe state --format
json` then `pipe check --model` reports exactly what `pipe check` does
on the fixture and on resolved mirrors of nuwayze (102 findings), tick,
dex and word_alive. state_map_roundtrip_test.dart asserts the same in
process (model JSON is idempotent; findings identical; every rule fires,
so the comparison is not vacuous), and model_cli_test.dart covers the
CLI path.

--model refuses --write-baseline: a model does not record whether its
front end read every file, so the baseline could silently be partial -
the hazard the source path already refuses. A malformed model is a
PipeException, not a crash.

134 tests pass.
2026-09-22 14:31:03 -06:00

44 lines
1.4 KiB
Dart

import 'dart:convert';
import 'package:pipe/pipe.dart';
import 'package:test/test.dart';
/// The state-map seam: rules read only the model, so a model that went through
/// JSON - as one written by a separate front end would - must produce exactly
/// the same report as the in-process model. If a rule quietly depended on
/// something the JSON does not carry, this is where it shows.
void main() {
late ProjectAnalysis analysis;
setUpAll(() async {
analysis = await analyzeProject(
await loadProject('test/fixtures/stuck_loading'),
concurrency: 1,
);
});
tearDownAll(() async {
await analysis.dispose();
});
StateFlow throughJson(StateFlow flow) => stateFlowFromJson(
jsonDecode(jsonEncode(flow.toJson())) as Map<String, Object?>,
);
test('the model survives JSON unchanged', () {
final once = jsonEncode(analysis.flow.toJson());
final twice = jsonEncode(throughJson(analysis.flow).toJson());
expect(twice, once);
});
test('rules on the decoded model report exactly the same findings', () {
final direct = jsonEncode(runChecks(flow: analysis.flow).toJson());
final decoded = jsonEncode(
runChecks(flow: throughJson(analysis.flow)).toJson(),
);
expect(decoded, direct);
// Not vacuous: every rule fires somewhere on the fixture.
final rules = runChecks(flow: analysis.flow).findings.map((f) => f.rule);
expect(rules.toSet(), containsAll(allRules));
});
}