- Android NotificationListenerService plugin + queue store, MethodChannel bridge, and Flutter ingest worker / access controller - source_apps allowlist (DAO, repo, settings + source_apps screen) - schema v8 migration + decision_gate / dedup / draft_codec updates - l10n strings and tests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
3.7 KiB
Dart
90 lines
3.7 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:drift/drift.dart' hide isNull, isNotNull;
|
|
import 'package:drift/native.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:new_budget/src/core/database/app_database.dart';
|
|
import 'package:new_budget/src/core/database/converters/enum_converters.dart';
|
|
|
|
/// Тест миграции v5 → v6 (habit-tracking).
|
|
///
|
|
/// Поднимаем актуальную (v6) схему на файле, затем «откатываем» её до v5,
|
|
/// удаляя новые колонки и проставляя user_version = 5. При повторном открытии
|
|
/// срабатывает `onUpgrade(5 → 6)`, который должен снова добавить колонки.
|
|
void main() {
|
|
late File file;
|
|
|
|
setUp(() {
|
|
final dir = Directory.systemTemp.createTempSync('nb_migration_test');
|
|
file = File('${dir.path}/test.sqlite');
|
|
});
|
|
|
|
tearDown(() {
|
|
if (file.existsSync()) file.deleteSync();
|
|
final parent = file.parent;
|
|
if (parent.existsSync()) parent.deleteSync(recursive: true);
|
|
});
|
|
|
|
test('onUpgrade 5 → 6 добавляет obligation/impulse/habitTrackingEnabled',
|
|
() async {
|
|
const userId = 'user-1';
|
|
const accountId = 'account-1';
|
|
|
|
// 1. Создаём актуальную схему (v6) и наполняем FK-цепочку.
|
|
final dbV6 = AppDatabase.forTesting(NativeDatabase(file));
|
|
await dbV6.usersDao
|
|
.insertUser(UsersTableCompanion.insert(id: userId, name: 'Тест'));
|
|
await dbV6.accountsDao.insertAccount(
|
|
AccountsTableCompanion.insert(
|
|
id: accountId,
|
|
userId: userId,
|
|
name: 'Основной',
|
|
),
|
|
);
|
|
|
|
// 2. «Откатываем» схему до v5: убираем колонки v6 И артефакты v7/v8
|
|
// (иначе onUpgrade 5→8 попытается создать их повторно).
|
|
await dbV6.customStatement(
|
|
'ALTER TABLE transactions DROP COLUMN obligation');
|
|
await dbV6.customStatement('ALTER TABLE transactions DROP COLUMN impulse');
|
|
await dbV6.customStatement(
|
|
'ALTER TABLE settings DROP COLUMN habit_tracking_enabled');
|
|
await dbV6.customStatement(
|
|
'ALTER TABLE account_bindings DROP COLUMN is_default');
|
|
await dbV6.customStatement('DROP TABLE source_apps');
|
|
await dbV6.customStatement('ALTER TABLE parse_rules DROP COLUMN tx_type');
|
|
await dbV6.customStatement('PRAGMA user_version = 5');
|
|
await dbV6.close();
|
|
|
|
// 3. Повторное открытие запускает onUpgrade(5 → 6).
|
|
final dbMigrated = AppDatabase.forTesting(NativeDatabase(file));
|
|
addTearDown(dbMigrated.close);
|
|
|
|
// 4. Колонки снова доступны: запись с оценками проходит round-trip.
|
|
final repoRow = await dbMigrated.transactionsDao.findById('tx-1');
|
|
expect(repoRow, isNull);
|
|
|
|
await dbMigrated.transactionsDao.insertTransaction(
|
|
TransactionsTableCompanion.insert(
|
|
id: 'tx-1',
|
|
userId: userId,
|
|
accountId: accountId,
|
|
amount: 1000,
|
|
date: DateTime(2024, 6, 1),
|
|
obligation: const Value(SpendingObligation.required),
|
|
impulse: const Value(SpendingImpulse.impulsive),
|
|
),
|
|
);
|
|
final tx = await dbMigrated.transactionsDao.findById('tx-1');
|
|
expect(tx!.obligation, SpendingObligation.required);
|
|
expect(tx.impulse, SpendingImpulse.impulsive);
|
|
|
|
// settings.habitTrackingEnabled присутствует с дефолтом false.
|
|
await dbMigrated.settingsDao.upsertSettings(
|
|
SettingsTableCompanion.insert(userId: userId),
|
|
);
|
|
final settings = await dbMigrated.settingsDao.getSettingsByUser(userId);
|
|
expect(settings!.habitTrackingEnabled, isFalse);
|
|
});
|
|
}
|