testsAndMisc/billsplit/test/eparagon_parser_test.dart
Krzysztof Rudnicki ed1a224ce7 Format last three markdown files; cap billsplit's Android build memory
Brings the repo to prettier-clean across all 525 tracked md/json/yaml files.
The markdown edits are cosmetic: escaping `L*` so it stops rendering as
emphasis, normalising `*em*` to `_em_`, and aligning tables.

billsplit/android/gradle.properties shipped from the Flutter template asking
for `-Xmx8G -XX:MaxMetaspaceSize=4G` — more than this machine has. Lowered to
768m/512m and, more importantly, cut the number of JVMs: Kotlin compiles
in-process, no persistent daemon, no parallel execution, one worker.

Also repairs a regression an earlier prettier pass introduced. That pass
pretty-printed test/fixtures/sample_receipt.json, but
`accepts bare JPK payload and bare JWS token` reads the fixture as raw TEXT
with a regex matching `"data":"..."` with no space after the colon. The
inserted space made the match null and the null-check operator threw.
Comparing the parsed JSON for equality had passed and proved nothing about
that raw-text contract.

The fixture is restored to its original minified bytes — a real e-paragon
arrives as single-line JSON — and test/fixtures/ is now in .prettierignore so
captured payloads stay byte-faithful. The regex also tolerates whitespace now
and asserts the match is non-null with a reason, so the same change would fail
readably instead of crashing. Verified against both the minified and the
pretty-printed fixture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 13:25:48 +02:00

87 lines
3.1 KiB
Dart

import 'dart:io';
import 'package:billsplit/domain/eparagon_parser.dart';
import 'package:billsplit/domain/models.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final fixture = File('test/fixtures/sample_receipt.json').readAsStringSync();
test('parses the real Biedronka e-paragon wrapper', () {
final res = parseEParagonDetailed(fixture);
final r = res.receipt;
// 49 goods lines + 1 deposit line.
expect(r.items.length, 50);
final deposit =
r.items.where((i) => i.category == Categories.deposit).toList();
expect(deposit.length, 1);
expect(deposit.single.totalGr, 700);
expect(deposit.single.qty, 14);
// Goods sum matches receipt sumaBrutto; paid total includes deposit.
final goods = r.items
.where((i) => i.category != Categories.deposit)
.fold(0, (s, i) => s + i.totalGr);
expect(goods, 59055);
expect(r.paidTotalGr, 59755);
expect(res.warnings, isEmpty);
// Alcohol detection: exactly vodka + beer, drinkers-only by default.
final alcohol =
r.items.where((i) => i.category == Categories.alcohol).toList();
expect(alcohol.length, 2);
expect(
alcohol.map((i) => i.name).join(' | ').toLowerCase(),
allOf(contains('wódka'), contains('piwo')),
);
for (final a in alcohol) {
expect(a.assignment.mode, AssignMode.drinkers);
}
expect(alcohol.fold(0, (s, i) => s + i.totalGr), 10872);
// Mixers tagged but split among everyone.
final mixers =
r.items.where((i) => i.category == Categories.mixer).toList();
expect(mixers.length, greaterThanOrEqualTo(4));
for (final m in mixers) {
expect(m.assignment.mode, AssignMode.everyone);
}
// Name cleanup: VAT letter stripped, spaces collapsed.
expect(r.items.first.name, 'Tonic');
// Weight-based quantity with comma decimal.
final kielbasa =
r.items.firstWhere((i) => i.name.startsWith('KiełbBrocka'));
expect(kielbasa.qty, closeTo(0.871, 1e-9));
// Discount captured (vodka -28.00).
final vodka = r.items.firstWhere((i) => i.name.startsWith('WódkaB'));
expect(vodka.discountGr, -2800);
expect(vodka.totalGr, 7998);
});
test('accepts bare JPK payload and bare JWS token', () {
final res = parseEParagonDetailed(fixture);
expect(res.receipt.items, isNotEmpty);
// Raw JWS from the wrapper should parse identically. Tolerate whitespace
// around the colon: the fixture is minified as the device emits it, but a
// pretty-printed payload is equally valid JSON and must not fail here.
final match = RegExp(r'"data"\s*:\s*"([^"]+)"').firstMatch(fixture);
expect(match, isNotNull, reason: 'fixture must carry a "data" JWS field');
final jws = match!.group(1)!;
final fromJws = parseEParagon(jws);
expect(fromJws.items.length, res.receipt.items.length);
});
test('rejects garbage input with a clear error', () {
expect(
() => parseEParagon('not json'),
throwsA(isA<EParagonParseException>()),
);
expect(
() => parseEParagon('{"foo": 1}'),
throwsA(isA<EParagonParseException>()),
);
});
}