add import export and items

This commit is contained in:
atseirjo
2026-10-28 08:10:55 +01:00
parent 07a1bb02fd
commit 4a1731f986
22 changed files with 1118 additions and 106 deletions
+234
View File
@@ -0,0 +1,234 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:ertl_filament_verwaltung/controller/export_controller.dart';
import 'package:ertl_filament_verwaltung/controller/home_controller.dart';
import 'package:ertl_filament_verwaltung/helper/sample_routes.dart';
import 'package:ertl_filament_verwaltung/helper/storage_keys.dart';
import 'package:ertl_filament_verwaltung/models/anwendung_model.dart';
import 'package:ertl_filament_verwaltung/models/backup_model.dart';
import 'package:ertl_filament_verwaltung/models/bauteil_model.dart';
import 'package:ertl_filament_verwaltung/models/kauf_filament_model.dart';
import 'package:ertl_filament_verwaltung/pages/export_view.dart';
import 'package:ertl_filament_verwaltung/pages/home_view.dart';
import 'package:ertl_filament_verwaltung/services/backup_file_service.dart';
import 'package:ertl_filament_verwaltung/services/get_storage_services.dart';
import 'package:ertl_filament_verwaltung/theme/app_theme.dart';
import 'helpers/memory_storage_service.dart';
class FakeBackupFiles extends BackupFileService {
String? source;
String? exported;
bool cancelSave = false;
@override
Future<String?> open() async => source;
@override
Future<bool> save(String json, String name) async {
if (cancelSave) return false;
exported = json;
return true;
}
}
class FailOnceStorage extends MemoryStorageService {
String? failKey;
@override
Future<void> saveModelList(
String key,
List<Map<String, dynamic>> items,
) async {
if (failKey == key) {
failKey = null;
throw StateError('Write failed');
}
await super.saveModelList(key, items);
}
}
void main() {
late FailOnceStorage storage;
late FakeBackupFiles files;
late ExportController controller;
Map<String, List<Map<String, dynamic>>> fixture() => {
StorageKeys.filamente: [
KaufFilamentModel(
filamentId: 'f1',
kaufdatum: DateTime(2026),
haendler: 'Shop',
marke: 'Marke',
material: 'PA12',
preis: 20,
farbe: 'Grün',
gewicht: '1000',
).toJson(),
],
StorageKeys.anwendungen: [
AnwendungModel(
anwendungId: 'p1',
filamentId: 'f1',
erstelltAm: DateTime(2026),
druckbettTemperatur: 60,
druckTemperatur: 220,
bauraumGeschlossen: true,
).toJson(),
],
StorageKeys.bauteile: [
BauteilModel(
bauteilId: 'b1',
erstelltAm: DateTime(2026),
name: 'Halter',
verwendung: 'Werkstatt',
druckdauerMinuten: 60,
gewichtGramm: 40,
abfallGramm: 10,
filamente: [
const BauteilFilamentModel(
filamentId: 'f1',
anwendungId: 'p1',
verbrauchGramm: 50,
),
],
fotoBase64: 'aGVsbG8=',
).toJson(),
],
StorageKeys.materialien: [
{
'werte': ['PA12', 'PLA'],
},
],
};
setUp(() {
Get.testMode = true;
storage = FailOnceStorage();
Get.put<GetStorageService>(storage);
files = FakeBackupFiles();
controller = Get.put(ExportController(fileService: files));
});
tearDown(() => Get.reset());
test(
'Complete JSON backup restores photos, IDs, stock and custom materials',
() async {
storage.tables.addAll(fixture());
await controller.exportData();
expect(files.exported, isNotNull);
expect(BackupModel.decode(files.exported!).data, fixture());
storage.tables.clear();
final home = Get.put(HomeController());
files.source = files.exported;
await controller.selectImport();
expect(storage.tables, isEmpty);
await controller.importData();
expect(storage.tables, fixture());
expect(home.materialien, ['PA12', 'PLA']);
expect(home.restgewicht(home.filamente.single), 950);
expect(controller.hasError.value, isFalse);
},
);
test(
'Malformed, incomplete, future-version and duplicate data do not write',
() async {
storage.tables.addAll(fixture());
final valid =
jsonDecode(BackupModel(fixture()).encode()) as Map<String, dynamic>;
final badData = fixture();
badData[StorageKeys.filamente]!.add(
badData[StorageKeys.filamente]!.single,
);
for (final source in [
'not json',
'{}',
jsonEncode({...valid, 'version': 2}),
jsonEncode({
...valid,
'daten': {StorageKeys.filamente: []},
}),
BackupModel(badData).encode(),
]) {
files.source = source;
await controller.selectImport();
await controller.importData();
expect(controller.pendingBackup.value, isNull);
expect(controller.hasError.value, isTrue);
expect(storage.tables, fixture());
}
},
);
test('Cancelled dialogs leave data unchanged', () async {
storage.tables.addAll(fixture());
files.cancelSave = true;
await controller.exportData();
await controller.selectImport();
expect(controller.message.value, isNull);
expect(controller.pendingBackup.value, isNull);
expect(storage.tables, fixture());
});
test('A failed import restores previously written tables', () async {
storage.tables.addAll(fixture());
files.source = BackupModel({for (final key in BackupModel.keys) key: []})
.encode();
await controller.selectImport();
storage.failKey = StorageKeys.bauteile;
await controller.importData();
expect(storage.tables, fixture());
expect(controller.hasError.value, isTrue);
expect(controller.isBusy.value, isFalse);
});
testWidgets('Import preview requires confirmation and works on a phone', (
tester,
) async {
tester.view.physicalSize = const Size(320, 640);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
storage.tables.addAll(fixture());
files.source = BackupModel({for (final key in BackupModel.keys) key: []})
.encode();
await tester.pumpWidget(
GetMaterialApp(theme: AppTheme.light, home: const ExportView()),
);
await controller.selectImport();
await tester.pumpAndSettle();
await tester.ensureVisible(find.text('Daten ersetzen und importieren'));
await tester.tap(find.text('Daten ersetzen und importieren'));
await tester.pumpAndSettle();
await tester.tap(find.text('Abbrechen'));
await tester.pumpAndSettle();
expect(storage.tables, fixture());
await tester.tap(find.text('Daten ersetzen und importieren'));
await tester.pumpAndSettle();
await tester.tap(find.text('Daten ersetzen'));
await tester.pumpAndSettle();
expect(storage.tables.values.every((value) => value.isEmpty), isTrue);
expect(tester.takeException(), isNull);
});
testWidgets('Hamburger menu opens the backup route', (tester) async {
await tester.pumpWidget(
GetMaterialApp(
theme: AppTheme.light,
initialRoute: HomeView.routeName,
getPages: SampleRouts.samplePages,
),
);
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.menu));
await tester.pumpAndSettle();
await tester.tap(find.text('Daten exportieren / importieren'));
await tester.pumpAndSettle();
expect(find.byType(ExportView), findsOneWidget);
expect(tester.takeException(), isNull);
});
}