Commit 53ad601

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-12-22 10:02:24
ensure importing from old versions works
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent fd53a6d
Changed files (3)
lib
test
model
lib/model/export_import/csv_converter.dart
@@ -63,8 +63,9 @@ class CsvConverter {
     final List<ExportColumn> columns = [];
     for (final titleText in lines.removeAt(0)) {
       assert(titleText is String);
+      final formattedTitleText = (titleText as String).trim();
       final column = availableColumns.firstWhere(
-              (c) => c.csvTitle == titleText
+              (c) => c.csvTitle == formattedTitleText
                   && c.restoreAbleType != null);
       if (column == null) return RecordParsingResult.err(RecordParsingErrorUnknownColumn(titleText));
       columns.add(column);
@@ -85,14 +86,21 @@ class CsvConverter {
       for (int fieldIndex = 0; fieldIndex < columns.length; fieldIndex++) {
         assert(currentLine[fieldIndex] is String);
         final piece = columns[fieldIndex].decode(currentLine[fieldIndex]);
-        if (piece?.$1 != columns[fieldIndex].restoreAbleType) { // validation
+        // Validate that the column parsed the expected type.
+        // Null can be the result of empty fields.
+        if (piece?.$1 != columns[fieldIndex].restoreAbleType
+            && piece != null) { // TODO: consider making some RowDataFieldType values nullable and handling this in the parser.
           return RecordParsingResult.err(RecordParsingErrorUnparsableField(currentLineNumber, currentLine[fieldIndex]));
         }
         if (piece != null) recordPieces.add(piece);
       }
 
-      final DateTime timestamp = recordPieces.firstWhere(
-              (piece) => piece.$1 == RowDataFieldType.timestamp).$2;
+      final DateTime? timestamp = recordPieces.firstWhereOrNull(
+              (piece) => piece.$1 == RowDataFieldType.timestamp)?.$2;
+      if (timestamp == null) {
+        return RecordParsingResult.err(RecordParsingErrorTimeNotRestoreable());
+      }
+
       final int? sys = recordPieces.firstWhereOrNull(
               (piece) => piece.$1 == RowDataFieldType.sys)?.$2;
       final int? dia = recordPieces.firstWhereOrNull(
lib/model/export_import/import_field_type.dart
@@ -17,7 +17,7 @@ enum RowDataFieldType {
   pul,
   /// Guarantees [String] is returned.
   notes,
-  @Deprecated('use needlePin instead') // TODO: implement conversion to needle pin?
+  @Deprecated('use needlePin instead. Can be removed in code as all colors can be expressed as needle pins') // TODO: implement conversion to needle pin?
   /// Guarantees [Color] is returned.
   color,
   /// Guarantees that the returned type is of type [MeasurementNeedlePin].
test/model/export_import/csv_converter_test.dart
@@ -1,4 +1,6 @@
 
+import 'dart:io';
+
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/export_import/csv_converter.dart';
 import 'package:blood_pressure_app/model/export_import/record_parsing_result.dart';
@@ -49,7 +51,216 @@ void main() {
             'equal to'));
     });
 
-    // TODO: test more
+    test('should import v1.0.0 measurements', () {
+      final text = File('test/model/export_import/exported_formats/v1.0.csv').readAsStringSync();
+
+      final converter = CsvConverter(
+          CsvExportSettings(),
+          ExportColumnsManager()
+      );
+      final parsed = converter.parse(text);
+      final records = parsed.getOr(failParse);
+      expect(records, isNotNull);
+      expect(records.length, 2);
+      expect(records, everyElement(isA<BloodPressureRecord>()));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+          .having((p0) => p0.systolic, 'systolic', 312)
+          .having((p0) => p0.diastolic, 'diastolic', 315)
+          .having((p0) => p0.pulse, 'pulse', 46)
+          .having((p0) => p0.notes.trim(), 'notes', 'testfkajkfb')
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+          .having((p0) => p0.systolic, 'systolic', 123)
+          .having((p0) => p0.diastolic, 'diastolic', 41)
+          .having((p0) => p0.pulse, 'pulse', 43)
+          .having((p0) => p0.notes.trim(), 'notes', '1214s3')
+      ));
+    });
+    test('should import v1.1.0 measurements', () {
+      final text = File('test/model/export_import/exported_formats/v1.1.0').readAsStringSync();
+
+      final converter = CsvConverter(
+          CsvExportSettings(),
+          ExportColumnsManager()
+      );
+      final parsed = converter.parse(text);
+      final records = parsed.getOr(failParse);
+      expect(records, isNotNull);
+      expect(records.length, 4);
+      expect(records, everyElement(isA<BloodPressureRecord>()));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+          .having((p0) => p0.systolic, 'systolic', 312)
+          .having((p0) => p0.diastolic, 'diastolic', 315)
+          .having((p0) => p0.pulse, 'pulse', 46)
+          .having((p0) => p0.notes.trim(), 'notes', 'testfkajkfb')
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+          .having((p0) => p0.systolic, 'systolic', 123)
+          .having((p0) => p0.diastolic, 'diastolic', 41)
+          .having((p0) => p0.pulse, 'pulse', 43)
+          .having((p0) => p0.notes.trim(), 'notes', '1214s3')
+      ));
+    });
+    test('should import v1.4.0 measurements', () {
+      final text = File('test/model/export_import/exported_formats/v1.4.0.CSV').readAsStringSync();
+
+      final converter = CsvConverter(
+          CsvExportSettings(),
+          ExportColumnsManager()
+      );
+      final parsed = converter.parse(text);
+      final records = parsed.getOr(failParse);
+      expect(records, isNotNull);
+      expect(records.length, 186);
+      expect(records, everyElement(isA<BloodPressureRecord>()));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+          .having((p0) => p0.systolic, 'systolic', 312)
+          .having((p0) => p0.diastolic, 'diastolic', 315)
+          .having((p0) => p0.pulse, 'pulse', 46)
+          .having((p0) => p0.notes, 'notes', 'testfkajkfb')
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+          .having((p0) => p0.systolic, 'systolic', 123)
+          .having((p0) => p0.diastolic, 'diastolic', 41)
+          .having((p0) => p0.pulse, 'pulse', 43)
+          .having((p0) => p0.notes, 'notes', '1214s3')
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 10893142303200)
+          .having((p0) => p0.systolic, 'systolic', 106)
+          .having((p0) => p0.diastolic, 'diastolic', 77)
+          .having((p0) => p0.pulse, 'pulse', 53)
+          .having((p0) => p0.notes, 'notes', '')
+      ));
+    });
+    test('should import v1.5.1 measurements', () {
+      final text = File('test/model/export_import/exported_formats/v1.5.1.csv').readAsStringSync();
+
+      final converter = CsvConverter(
+          CsvExportSettings(),
+          ExportColumnsManager()
+      );
+      final parsed = converter.parse(text);
+      final records = parsed.getOr(failParse);
+      expect(records, isNotNull);
+      expect(records.length, 185);
+      expect(records, everyElement(isA<BloodPressureRecord>()));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+          .having((p0) => p0.systolic, 'systolic', 312)
+          .having((p0) => p0.diastolic, 'diastolic', 315)
+          .having((p0) => p0.pulse, 'pulse', 46)
+          .having((p0) => p0.notes, 'notes', 'testfkajkfb')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+          .having((p0) => p0.systolic, 'systolic', 123)
+          .having((p0) => p0.diastolic, 'diastolic', 41)
+          .having((p0) => p0.pulse, 'pulse', 43)
+          .having((p0) => p0.notes, 'notes', '1214s3')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1077625200000)
+          .having((p0) => p0.systolic, 'systolic', 100)
+          .having((p0) => p0.diastolic, 'diastolic', 82)
+          .having((p0) => p0.pulse, 'pulse', 63)
+          .having((p0) => p0.notes, 'notes', '')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+    });
+    test('should import v1.5.7 measurements', () {
+      final text = File('test/model/export_import/exported_formats/v1.5.7.csv').readAsStringSync();
+
+      final converter = CsvConverter(
+          CsvExportSettings(),
+          ExportColumnsManager()
+      );
+      final parsed = converter.parse(text);
+      final records = parsed.getOr(failParse);
+      expect(records, isNotNull);
+      expect(records.length, 185);
+      expect(records, everyElement(isA<BloodPressureRecord>()));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+          .having((p0) => p0.systolic, 'systolic', 312)
+          .having((p0) => p0.diastolic, 'diastolic', 315)
+          .having((p0) => p0.pulse, 'pulse', 46)
+          .having((p0) => p0.notes, 'notes', 'testfkajkfb')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+          .having((p0) => p0.systolic, 'systolic', 123)
+          .having((p0) => p0.diastolic, 'diastolic', 41)
+          .having((p0) => p0.pulse, 'pulse', 43)
+          .having((p0) => p0.notes, 'notes', '1214s3')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1077625200000)
+          .having((p0) => p0.systolic, 'systolic', 100)
+          .having((p0) => p0.diastolic, 'diastolic', 82)
+          .having((p0) => p0.pulse, 'pulse', 63)
+          .having((p0) => p0.notes, 'notes', '')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+      // TODO: test color
+    });
+    test('should import v1.5.8 measurements', () {
+      final text = File('test/model/export_import/exported_formats/v1.5.8.csv').readAsStringSync();
+
+      final converter = CsvConverter(
+          CsvExportSettings(),
+          ExportColumnsManager()
+      );
+      final parsed = converter.parse(text);
+      final records = parsed.getOr(failParse);
+      expect(records, isNotNull);
+      expect(records.length, 9478);
+      expect(records, everyElement(isA<BloodPressureRecord>()));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1703175193324)
+          .having((p0) => p0.systolic, 'systolic', 123)
+          .having((p0) => p0.diastolic, 'diastolic', 43)
+          .having((p0) => p0.pulse, 'pulse', 53)
+          .having((p0) => p0.notes, 'notes', 'sdfsdfds')
+          .having((p0) => p0.needlePin?.color, 'pin', const Color(0xff69f0ae))
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1702883511000)
+          .having((p0) => p0.systolic, 'systolic', 114)
+          .having((p0) => p0.diastolic, 'diastolic', 71)
+          .having((p0) => p0.pulse, 'pulse', 66)
+          .having((p0) => p0.notes, 'notes', 'fsaf &_*¢|^✓[=%®©')
+          .having((p0) => p0.needlePin?.color.value, 'pin', Colors.lightGreen.value)
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1701034952000)
+          .having((p0) => p0.systolic, 'systolic', 125)
+          .having((p0) => p0.diastolic, 'diastolic', 77)
+          .having((p0) => p0.pulse, 'pulse', 60)
+          .having((p0) => p0.notes, 'notes', '')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+      expect(records, anyElement(isA<BloodPressureRecord>()
+          .having((p0) => p0.creationTime.millisecondsSinceEpoch, 'timestamp', 1077625200000)
+          .having((p0) => p0.systolic, 'systolic', 100)
+          .having((p0) => p0.diastolic, 'diastolic', 82)
+          .having((p0) => p0.pulse, 'pulse', 63)
+          .having((p0) => p0.notes, 'notes', '')
+          .having((p0) => p0.needlePin, 'pin', null)
+      ));
+      // TODO: test time columns
+    });
+    // TODO: test importing partially null fields
   });
 }