Files
OnBudget/test/features/notification_parsing/parser/account_resolver_test.dart
T
SandersandClaude Opus 4.8 4f99b80169 Replace account_bindings with per-app default account; build analytics charts
Notification parsing:
- Drop the account_bindings table/DAO/repo/entity/controller/screen; account
  resolution now goes senderToAccount rule -> source_apps.defaultAccountId
  (trusted) -> global default (untrusted -> Inbox), via v1->v2 migration.
- Add defaultAccountId to source_apps; per-app settings consolidated into
  source_app_detail_screen (/settings/parsing/apps/:pkg).
- Inbox auto-learns an app default on first Confirm/CreateRule; account picker
  on the card instead of a disabled button; parse_error_labels extracted.

Analytics:
- Replace placeholder screen with fl_chart cards (chart_card, chart_theme,
  month_stepper, month_math domain helper); slim down habit_analysis_screen.

Android: add launcher icon (adaptive foreground + colors.xml) and app_name.

Tests: migration_v2, analytics (screen/month_math), AI retry, inbox
visibility; update resolver/gate/inbox suites for the new resolution path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:09:12 +03:00

88 lines
2.9 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:new_budget/src/features/notification_parsing/data/parser/account_resolver.dart';
import 'package:new_budget/src/features/notification_parsing/domain/entities/parse_rule.dart';
import 'package:new_budget/src/features/notification_parsing/domain/enums.dart';
const _userId = 'u1';
final _now = DateTime(2026, 1, 1);
ParseRule _senderRule(String pattern, String accountId, {bool enabled = true}) =>
ParseRule(
id: 'sr-$pattern',
userId: _userId,
kind: ParseRuleKind.senderToAccount,
matchMode: MatchMode.contains,
pattern: pattern,
accountId: accountId,
enabled: enabled,
createdAt: _now,
);
AccountResolution _resolve({
List<ParseRule> senderRules = const [],
String? appDefaultAccountId,
String? globalDefaultAccountId,
String body = 'Покупка 1240 ₽ Карта *3456 PYATEROCHKA',
}) =>
resolveAccount(
body: body,
senderRules: senderRules,
appDefaultAccountId: appDefaultAccountId,
globalDefaultAccountId: globalDefaultAccountId,
);
void main() {
group('resolveAccount (лестница §B)', () {
test('#1 senderToAccount rule → 90, trusted; бьёт оба дефолта', () {
final r = _resolve(
senderRules: [_senderRule('*3456', 'acc-rule')],
appDefaultAccountId: 'acc-app',
globalDefaultAccountId: 'acc-global',
);
expect(r.accountId, 'acc-rule');
expect(r.score, 90);
expect(r.trusted, isTrue);
expect(r.source, AccountSource.senderRule);
});
test('#1 несовпавшее/выключенное правило пропускается', () {
final r = _resolve(
senderRules: [
_senderRule('НЕ СОВПАДЁТ', 'acc-miss'),
_senderRule('*3456', 'acc-off', enabled: false),
],
appDefaultAccountId: 'acc-app',
);
expect(r.accountId, 'acc-app');
expect(r.source, AccountSource.appDefault);
});
test('#2 дефолт приложения → 75, trusted', () {
final r = _resolve(
appDefaultAccountId: 'acc-app',
globalDefaultAccountId: 'acc-global',
);
expect(r.accountId, 'acc-app');
expect(r.score, 75);
expect(r.trusted, isTrue);
expect(r.source, AccountSource.appDefault);
});
test('#3 глобальный дефолт → 40, NOT trusted (смена поведения)', () {
final r = _resolve(globalDefaultAccountId: 'acc-global');
expect(r.accountId, 'acc-global');
expect(r.score, 40);
expect(r.trusted, isFalse);
expect(r.source, AccountSource.globalDefault);
});
test('#4 ничего → null, 15, NOT trusted', () {
final r = _resolve();
expect(r.accountId, isNull);
expect(r.score, 15);
expect(r.trusted, isFalse);
expect(r.source, AccountSource.none);
});
});
}