From 085cb9d67fbd0dcefc5e703238902bdad342e68b Mon Sep 17 00:00:00 2001 From: danny Date: Sat, 30 Mar 2024 14:09:54 +0100 Subject: [PATCH] Added location model tests --- .../lib/accessory/accessory_model.dart | 8 +- .../lib/accessory/accessory_registry.dart | 5 +- macless_haystack/lib/findMy/models.dart | 8 +- .../lib/location/location_model.dart | 2 +- macless_haystack/pubspec.yaml | 8 +- .../accessory/accessory_registry_test.dart | 114 ++++ .../accessory_registry_test.mocks.dart | 506 ++++++++++++++++++ 7 files changed, 642 insertions(+), 9 deletions(-) create mode 100644 macless_haystack/test/accessory/accessory_registry_test.dart create mode 100644 macless_haystack/test/accessory/accessory_registry_test.mocks.dart diff --git a/macless_haystack/lib/accessory/accessory_model.dart b/macless_haystack/lib/accessory/accessory_model.dart index 72f777b..53fe6d5 100644 --- a/macless_haystack/lib/accessory/accessory_model.dart +++ b/macless_haystack/lib/accessory/accessory_model.dart @@ -88,6 +88,8 @@ class Accessory { /// Stores address information about the current location. Future place = Future.value(null); + LocationModel locationModel = LocationModel(); + /// Creates an accessory with the given properties. Accessory( {required this.id, @@ -113,7 +115,7 @@ class Accessory { void _init() { if (_lastLocation != null) { - place = LocationModel.getAddress(_lastLocation!); + place = locationModel.getAddress(_lastLocation!); } } @@ -160,10 +162,12 @@ class Accessory { set lastLocation(LatLng? newLocation) { _lastLocation = newLocation; if (_lastLocation != null) { - place = LocationModel.getAddress(_lastLocation!); + place = locationModel.getAddress(_lastLocation!); } } + + /// The display icon of the accessory. IconData get icon { IconData? icon = AccessoryIconModel.mapIcon(_icon); diff --git a/macless_haystack/lib/accessory/accessory_registry.dart b/macless_haystack/lib/accessory/accessory_registry.dart index c6b403f..1880ab3 100644 --- a/macless_haystack/lib/accessory/accessory_registry.dart +++ b/macless_haystack/lib/accessory/accessory_registry.dart @@ -14,7 +14,7 @@ const accessoryStorageKey = 'ACCESSORIES'; const historStorageKey = 'HISTORY'; class AccessoryRegistry extends ChangeNotifier { - final _storage = const FlutterSecureStorage(); + var _storage = const FlutterSecureStorage(); List _accessories = []; bool loading = false; bool initialLoadFinished = false; @@ -55,6 +55,9 @@ class AccessoryRegistry extends ChangeNotifier { notifyListeners(); } + set setStorage(FlutterSecureStorage s){ + _storage = s; + } Future loadHistory() async { String? history = await _storage.read(key: historStorageKey); diff --git a/macless_haystack/lib/findMy/models.dart b/macless_haystack/lib/findMy/models.dart index 1336193..6531d5e 100644 --- a/macless_haystack/lib/findMy/models.dart +++ b/macless_haystack/lib/findMy/models.dart @@ -31,6 +31,11 @@ class FindMyLocationReport { FindMyLocationReport(this.latitude, this.longitude, this.accuracy, this.published, this.timestamp, this.confidence); + FindMyLocationReport.withHash( + this.latitude, this.longitude, this.timestamp, this.hash) { + accuracy = 0; + } + FindMyLocationReport.decrypted(this.result, this.base64privateKey, this.id) { hash = result['payload']; } @@ -50,7 +55,8 @@ class FindMyLocationReport { await Future.delayed(const Duration( milliseconds: 1)); //Is needed otherwise is executed synchron if (isEncrypted()) { - logger.d('Decrypting report with private key of ${getId()!.substring(0, 4)}'); + logger.d( + 'Decrypting report with private key of ${getId()!.substring(0, 4)}'); final unixTimestampInMillis = result["datePublished"]; final datePublished = DateTime.fromMillisecondsSinceEpoch(unixTimestampInMillis); diff --git a/macless_haystack/lib/location/location_model.dart b/macless_haystack/lib/location/location_model.dart index 0d688e9..19b01ce 100644 --- a/macless_haystack/lib/location/location_model.dart +++ b/macless_haystack/lib/location/location_model.dart @@ -107,7 +107,7 @@ class LocationModel extends ChangeNotifier { /// Returns the address for a given geolocation (latitude & longitude). /// /// Only works on mobile platforms with their local APIs. - static Future getAddress(LatLng? location) async { + Future getAddress(LatLng? location) async { if (location == null) { return null; } diff --git a/macless_haystack/pubspec.yaml b/macless_haystack/pubspec.yaml index f11f0f0..98e2c34 100644 --- a/macless_haystack/pubspec.yaml +++ b/macless_haystack/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: flutter_slidable: ^3.0.0 # Networking - http: ^1.1.0 + http: ^1.2.1 universal_html: ^2.2.3 # Cryptography @@ -70,9 +70,6 @@ dependencies: intl: ^0.19.0 logger: ^2.0.1 - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - #cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: @@ -86,6 +83,9 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^3.0.1 + test: ^1.24.9 + mockito: ^5.4.4 + build_runner: ^2.4.9 # Configuration for flutter_launcher_icons flutter_icons: diff --git a/macless_haystack/test/accessory/accessory_registry_test.dart b/macless_haystack/test/accessory/accessory_registry_test.dart new file mode 100644 index 0000000..f43687c --- /dev/null +++ b/macless_haystack/test/accessory/accessory_registry_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:geocoding/geocoding.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:macless_haystack/accessory/accessory_model.dart'; +import 'package:macless_haystack/accessory/accessory_registry.dart'; +import 'package:macless_haystack/findMy/models.dart'; +import 'package:macless_haystack/location/location_model.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +import 'accessory_registry_test.mocks.dart'; + +// class MockLocationModel extends Mock implements LocationModel {} + +@GenerateMocks([LocationModel, FlutterSecureStorage]) +void main() { + var locationModel = MockLocationModel(); + var registry = AccessoryRegistry(); + Accessory accessory = Accessory( + id: '', + name: '', + hashedPublicKey: '', + datePublished: DateTime.now(), + additionalKeys: List.empty()); + setUp(() { + when(locationModel.getAddress(any)) + .thenAnswer((_) async => const Placemark()); + registry.setStorage = MockFlutterSecureStorage(); + accessory.locationModel = locationModel; + }); + + test('Add location history same location unsorted with no entries before', + () async { + List reports = []; + // 8 o'clock + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 8, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + //6 o'clock + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 6, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + //10 o'clock + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 10, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + + await registry.fillLocationHistory(reports, accessory); + var latest = accessory.latestHistoryEntry(); + expect(DateTime(2024, 1, 1, 10, 0, 0), latest); + expect(1, accessory.locationHistory.length); + expect(DateTime(2024, 1, 1, 10, 0, 0), + accessory.locationHistory.elementAt(0).end); + expect(DateTime(2024, 1, 1, 6, 0, 0), + accessory.locationHistory.elementAt(0).start); + }); + + test( + 'Add location history different location unsorted with no entries before', + () async { + List reports = []; + // 8 o'clock 1st location + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 8, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + //10 o'clock second location + reports.add(FindMyLocationReport.withHash( + 2, + 2, + DateTime(2024, 1, 1, 10, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + // 9 o'clock first location + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 9, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + //12 o'clock 1st location + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 12, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + await registry.fillLocationHistory(reports, accessory); + var locationHistory = accessory.locationHistory; + expect(3, locationHistory.length); + + var latest = accessory.datePublished; + var lastLocation = accessory.lastLocation; + var endOfFirstEntry = accessory.latestHistoryEntry(); + + expect(endOfFirstEntry, DateTime(2024, 1, 1, 9, 0, 0)); + expect(latest, DateTime(2024, 1, 1, 12, 0, 0)); + expect(lastLocation, const LatLng(1, 2)); + + expect(locationHistory.elementAt(0).start, DateTime(2024, 1, 1, 8, 0, 0)); + expect(locationHistory.elementAt(0).end, DateTime(2024, 1, 1, 9, 0, 0)); + + expect(locationHistory.elementAt(1).start, DateTime(2024, 1, 1, 10, 0, 0)); + expect(locationHistory.elementAt(1).end, DateTime(2024, 1, 1, 10, 0, 0)); + + expect(locationHistory.elementAt(2).start, DateTime(2024, 1, 1, 12, 0, 0)); + expect(locationHistory.elementAt(2).end, DateTime(2024, 1, 1, 12, 0, 0)); + }); +} diff --git a/macless_haystack/test/accessory/accessory_registry_test.mocks.dart b/macless_haystack/test/accessory/accessory_registry_test.mocks.dart new file mode 100644 index 0000000..daa8291 --- /dev/null +++ b/macless_haystack/test/accessory/accessory_registry_test.mocks.dart @@ -0,0 +1,506 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in macless_haystack/test/accessory/accessory_registry_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i8; +import 'dart:ui' as _i9; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart' as _i4; +import 'package:geocoding/geocoding.dart' as _i7; +import 'package:latlong2/latlong.dart' as _i6; +import 'package:location/location.dart' as _i2; +import 'package:logger/logger.dart' as _i3; +import 'package:macless_haystack/location/location_model.dart' as _i5; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeLocation_0 extends _i1.SmartFake implements _i2.Location { + _FakeLocation_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLogger_1 extends _i1.SmartFake implements _i3.Logger { + _FakeLogger_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIOSOptions_2 extends _i1.SmartFake implements _i4.IOSOptions { + _FakeIOSOptions_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAndroidOptions_3 extends _i1.SmartFake + implements _i4.AndroidOptions { + _FakeAndroidOptions_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLinuxOptions_4 extends _i1.SmartFake implements _i4.LinuxOptions { + _FakeLinuxOptions_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWindowsOptions_5 extends _i1.SmartFake + implements _i4.WindowsOptions { + _FakeWindowsOptions_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebOptions_6 extends _i1.SmartFake implements _i4.WebOptions { + _FakeWebOptions_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMacOsOptions_7 extends _i1.SmartFake implements _i4.MacOsOptions { + _FakeMacOsOptions_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [LocationModel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockLocationModel extends _i1.Mock implements _i5.LocationModel { + MockLocationModel() { + _i1.throwOnMissingStub(this); + } + + @override + set here(_i6.LatLng? _here) => super.noSuchMethod( + Invocation.setter( + #here, + _here, + ), + returnValueForMissingStub: null, + ); + + @override + set herePlace(_i7.Placemark? _herePlace) => super.noSuchMethod( + Invocation.setter( + #herePlace, + _herePlace, + ), + returnValueForMissingStub: null, + ); + + @override + set locationStream( + _i8.StreamSubscription<_i2.LocationData>? _locationStream) => + super.noSuchMethod( + Invocation.setter( + #locationStream, + _locationStream, + ), + returnValueForMissingStub: null, + ); + + @override + bool get initialLocationSet => (super.noSuchMethod( + Invocation.getter(#initialLocationSet), + returnValue: false, + ) as bool); + + @override + set initialLocationSet(bool? _initialLocationSet) => super.noSuchMethod( + Invocation.setter( + #initialLocationSet, + _initialLocationSet, + ), + returnValueForMissingStub: null, + ); + + @override + _i2.Location get location => (super.noSuchMethod( + Invocation.getter(#location), + returnValue: _FakeLocation_0( + this, + Invocation.getter(#location), + ), + ) as _i2.Location); + + @override + set location(_i2.Location? _location) => super.noSuchMethod( + Invocation.setter( + #location, + _location, + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Logger get logger => (super.noSuchMethod( + Invocation.getter(#logger), + returnValue: _FakeLogger_1( + this, + Invocation.getter(#logger), + ), + ) as _i3.Logger); + + @override + set logger(_i3.Logger? _logger) => super.noSuchMethod( + Invocation.setter( + #logger, + _logger, + ), + returnValueForMissingStub: null, + ); + + @override + bool get hasListeners => (super.noSuchMethod( + Invocation.getter(#hasListeners), + returnValue: false, + ) as bool); + + @override + _i8.Future requestLocationAccess() => (super.noSuchMethod( + Invocation.method( + #requestLocationAccess, + [], + ), + returnValue: _i8.Future.value(false), + ) as _i8.Future); + + @override + _i8.Future requestLocationUpdates() => (super.noSuchMethod( + Invocation.method( + #requestLocationUpdates, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + void cancelLocationUpdates() => super.noSuchMethod( + Invocation.method( + #cancelLocationUpdates, + [], + ), + returnValueForMissingStub: null, + ); + + @override + _i8.Future<_i7.Placemark?> getAddress(_i6.LatLng? location) => + (super.noSuchMethod( + Invocation.method( + #getAddress, + [location], + ), + returnValue: _i8.Future<_i7.Placemark?>.value(), + ) as _i8.Future<_i7.Placemark?>); + + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + Invocation.method( + #addListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + Invocation.method( + #removeListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void dispose() => super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void notifyListeners() => super.noSuchMethod( + Invocation.method( + #notifyListeners, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [FlutterSecureStorage]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlutterSecureStorage extends _i1.Mock + implements _i4.FlutterSecureStorage { + MockFlutterSecureStorage() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.IOSOptions get iOptions => (super.noSuchMethod( + Invocation.getter(#iOptions), + returnValue: _FakeIOSOptions_2( + this, + Invocation.getter(#iOptions), + ), + ) as _i4.IOSOptions); + + @override + _i4.AndroidOptions get aOptions => (super.noSuchMethod( + Invocation.getter(#aOptions), + returnValue: _FakeAndroidOptions_3( + this, + Invocation.getter(#aOptions), + ), + ) as _i4.AndroidOptions); + + @override + _i4.LinuxOptions get lOptions => (super.noSuchMethod( + Invocation.getter(#lOptions), + returnValue: _FakeLinuxOptions_4( + this, + Invocation.getter(#lOptions), + ), + ) as _i4.LinuxOptions); + + @override + _i4.WindowsOptions get wOptions => (super.noSuchMethod( + Invocation.getter(#wOptions), + returnValue: _FakeWindowsOptions_5( + this, + Invocation.getter(#wOptions), + ), + ) as _i4.WindowsOptions); + + @override + _i4.WebOptions get webOptions => (super.noSuchMethod( + Invocation.getter(#webOptions), + returnValue: _FakeWebOptions_6( + this, + Invocation.getter(#webOptions), + ), + ) as _i4.WebOptions); + + @override + _i4.MacOsOptions get mOptions => (super.noSuchMethod( + Invocation.getter(#mOptions), + returnValue: _FakeMacOsOptions_7( + this, + Invocation.getter(#mOptions), + ), + ) as _i4.MacOsOptions); + + @override + _i8.Future write({ + required String? key, + required String? value, + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, + }) => + (super.noSuchMethod( + Invocation.method( + #write, + [], + { + #key: key, + #value: value, + #iOptions: iOptions, + #aOptions: aOptions, + #lOptions: lOptions, + #webOptions: webOptions, + #mOptions: mOptions, + #wOptions: wOptions, + }, + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future read({ + required String? key, + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, + }) => + (super.noSuchMethod( + Invocation.method( + #read, + [], + { + #key: key, + #iOptions: iOptions, + #aOptions: aOptions, + #lOptions: lOptions, + #webOptions: webOptions, + #mOptions: mOptions, + #wOptions: wOptions, + }, + ), + returnValue: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future containsKey({ + required String? key, + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, + }) => + (super.noSuchMethod( + Invocation.method( + #containsKey, + [], + { + #key: key, + #iOptions: iOptions, + #aOptions: aOptions, + #lOptions: lOptions, + #webOptions: webOptions, + #mOptions: mOptions, + #wOptions: wOptions, + }, + ), + returnValue: _i8.Future.value(false), + ) as _i8.Future); + + @override + _i8.Future delete({ + required String? key, + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [], + { + #key: key, + #iOptions: iOptions, + #aOptions: aOptions, + #lOptions: lOptions, + #webOptions: webOptions, + #mOptions: mOptions, + #wOptions: wOptions, + }, + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future> readAll({ + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, + }) => + (super.noSuchMethod( + Invocation.method( + #readAll, + [], + { + #iOptions: iOptions, + #aOptions: aOptions, + #lOptions: lOptions, + #webOptions: webOptions, + #mOptions: mOptions, + #wOptions: wOptions, + }, + ), + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); + + @override + _i8.Future deleteAll({ + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, + }) => + (super.noSuchMethod( + Invocation.method( + #deleteAll, + [], + { + #iOptions: iOptions, + #aOptions: aOptions, + #lOptions: lOptions, + #webOptions: webOptions, + #mOptions: mOptions, + #wOptions: wOptions, + }, + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); +}