From f6860478c205fb0ea3e14a8f7882a816d3ed6166 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 20 Nov 2023 19:23:06 -0500 Subject: [PATCH 1/5] test: device list --- .../sshnp/models/sshnp_device_list_test.dart | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/noports_core/test/sshnp/models/sshnp_device_list_test.dart diff --git a/packages/noports_core/test/sshnp/models/sshnp_device_list_test.dart b/packages/noports_core/test/sshnp/models/sshnp_device_list_test.dart new file mode 100644 index 000000000..c9b882e38 --- /dev/null +++ b/packages/noports_core/test/sshnp/models/sshnp_device_list_test.dart @@ -0,0 +1,48 @@ +import 'package:noports_core/sshnp_foundation.dart'; +import 'package:test/test.dart'; + +void main() { + group('SshnpDeviceList', () { + late SshnpDeviceList deviceList; + setUp(() => deviceList = SshnpDeviceList()); + test('public API', () { + expect( + deviceList.info, + allOf(isA>(), isEmpty), + ); + + expect( + deviceList.activeDevices, + allOf(isA>(), isEmpty), + ); + }); // test public API + + test('setActive', () { + expect(deviceList.info, isEmpty); + + deviceList.info['dev1'] = 'asdf'; + deviceList.setActive('dev1'); + expect( + deviceList.activeDevices, + allOf(hasLength(1), contains('dev1')), + ); + + deviceList.setActive('dev2'); + expect( + deviceList.activeDevices, + allOf(hasLength(1), isNot(contains('dev2'))), + ); + }); + + test('inactiveDevices', () { + deviceList.info['dev1'] = 'asdf'; + deviceList.info['dev2'] = 'jkl;'; + deviceList.setActive('dev1'); + + expect( + deviceList.inactiveDevices, + allOf(hasLength(1), contains('dev2')), + ); + }); // test inactiveDevices + }); // group SshnpDeviceList +} From 0b28f8f28e5190e27b0c88f08f697b04d34a4c70 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 21 Nov 2023 14:42:25 -0500 Subject: [PATCH 2/5] test: ssh key handler --- .../sshnp_ssh_key_handler.dart | 2 ++ .../sshnp_ssh_key_handler_mocks.dart | 8 ++++++ .../sshnp_ssh_key_handler_test.dart | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart create mode 100644 packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_test.dart diff --git a/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler.dart b/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler.dart index d718da805..451532a08 100644 --- a/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler.dart +++ b/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler.dart @@ -3,8 +3,10 @@ import 'package:noports_core/utils.dart'; mixin SshnpKeyHandler { @protected + @visibleForTesting AtSshKeyUtil get keyUtil; @protected + @visibleForTesting AtSshKeyPair? get identityKeyPair; } diff --git a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart new file mode 100644 index 000000000..f4e11388d --- /dev/null +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart @@ -0,0 +1,8 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:noports_core/sshnp_foundation.dart'; + +class MockSshnpKeyHandler extends Mock with SshnpKeyHandler {} + +class MockAtSshKeyUtil extends Mock implements AtSshKeyUtil {} + +class MockAtSshKeyPair extends Mock implements AtSshKeyPair {} diff --git a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_test.dart b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_test.dart new file mode 100644 index 000000000..f78029e89 --- /dev/null +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_test.dart @@ -0,0 +1,27 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:noports_core/sshnp_foundation.dart'; +import 'package:test/test.dart'; + +import 'sshnp_ssh_key_handler_mocks.dart'; + +void main() { + group('SshnpKeyHandler', () { + late MockSshnpKeyHandler mockKeyHandler; + late MockAtSshKeyUtil mockKeyUtil; + late MockAtSshKeyPair mockKeyPair; + + setUp(() { + mockKeyHandler = MockSshnpKeyHandler(); + mockKeyUtil = MockAtSshKeyUtil(); + mockKeyPair = MockAtSshKeyPair(); + }); + + test('public API', () { + when(() => mockKeyHandler.keyUtil).thenReturn(mockKeyUtil); + when(() => mockKeyHandler.identityKeyPair).thenReturn(mockKeyPair); + + expect(mockKeyHandler.keyUtil, isA()); + expect(mockKeyHandler.identityKeyPair, isA()); + }); // test public API + }); // group SshnpKeyHandler +} From 4d7662fecb9337e4104fc7d03b27cfddfe7cfae8 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 21 Nov 2023 16:27:53 -0500 Subject: [PATCH 3/5] refactor: simplify SshnpDartSshKeyHandler --- .../sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler.dart b/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler.dart index 68d38965d..ecefb5e43 100644 --- a/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler.dart +++ b/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler.dart @@ -1,11 +1,10 @@ import 'package:noports_core/sshnp_foundation.dart'; -mixin SshnpDartSshKeyHandler on SshnpCore implements SshnpKeyHandler { +mixin SshnpDartSshKeyHandler implements SshnpKeyHandler { @override DartSshKeyUtil get keyUtil => _sshKeyUtil; final DartSshKeyUtil _sshKeyUtil = DartSshKeyUtil(); @override - AtSshKeyPair? get identityKeyPair => _identityKeyPair; - AtSshKeyPair? _identityKeyPair; + AtSshKeyPair? identityKeyPair; } From 7f732e3769e5e243ef97d0467d94dc3ac61f66b0 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 21 Nov 2023 16:28:08 -0500 Subject: [PATCH 4/5] docs: explain the use of "on" keyword --- .../util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler.dart b/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler.dart index 8ba3f1e7b..ff253b5c7 100644 --- a/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler.dart +++ b/packages/noports_core/lib/src/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler.dart @@ -1,6 +1,8 @@ import 'package:noports_core/src/common/io_types.dart'; import 'package:noports_core/sshnp_foundation.dart'; +/// [SshnpLocalSshKeyHandler] uses variables from [SshnpCore] so it is a mixin +/// on [SshnpCore] mixin SshnpLocalSshKeyHandler on SshnpCore implements SshnpKeyHandler { @override LocalSshKeyUtil get keyUtil => _sshKeyUtil; From 5b4fe9f199832dffa87e58619e8d6d9e713df468 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 21 Nov 2023 16:29:55 -0500 Subject: [PATCH 5/5] test: Dart and Local SshKeyHandler tests --- .../test/sshnp/sshnp_core_mocks.dart | 4 +- .../test/sshnp/sshnp_core_test.dart | 10 +- .../sshnp_dart_ssh_key_handler_test.dart | 25 ++++ .../sshnp_local_ssh_key_handler_test.dart | 131 ++++++++++++++++++ .../sshnp_ssh_key_handler_mocks.dart | 33 +++++ 5 files changed, 196 insertions(+), 7 deletions(-) create mode 100644 packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler_test.dart create mode 100644 packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart diff --git a/packages/noports_core/test/sshnp/sshnp_core_mocks.dart b/packages/noports_core/test/sshnp/sshnp_core_mocks.dart index 9e054e5e3..29c7f4ae5 100644 --- a/packages/noports_core/test/sshnp/sshnp_core_mocks.dart +++ b/packages/noports_core/test/sshnp/sshnp_core_mocks.dart @@ -5,8 +5,8 @@ import 'sshnp_core_constants.dart'; /// Mocked Classes /// Stubbed [SshnpCore] (minimum viable implementation of [SshnpCore]) -class StubbedSshnpCore extends SshnpCore with StubbedAsyncInitializationMixin { - StubbedSshnpCore({ +class StubbedSshnp extends SshnpCore with StubbedAsyncInitializationMixin { + StubbedSshnp({ required super.atClient, required super.params, SshnpdChannel? sshnpdChannel, diff --git a/packages/noports_core/test/sshnp/sshnp_core_test.dart b/packages/noports_core/test/sshnp/sshnp_core_test.dart index 99a1fde1c..ca290106e 100644 --- a/packages/noports_core/test/sshnp/sshnp_core_test.dart +++ b/packages/noports_core/test/sshnp/sshnp_core_test.dart @@ -35,7 +35,7 @@ void main() { stubbedCompleteInitialization = FunctionStub(); }); - /// When declaration setup for the constructor of [StubbedSshnpCore] + /// When declaration setup for the constructor of [StubbedSshnp] whenConstructor({bool verbose = false}) { when(() => mockParams.device).thenReturn('mydevice'); when(() => mockParams.localPort).thenReturn(0); @@ -44,7 +44,7 @@ void main() { when(() => mockAtClient.setPreferences(any())).thenReturn(null); } - /// When declaration setup for the initialization of [StubbedSshnpCore] + /// When declaration setup for the initialization of [StubbedSshnp] whenInitialization({AtSshKeyPair? identityKeyPair}) { when(() => stubbedCallInitialization()).thenAnswer((_) async {}); when(() => stubbedInitialize()).thenAnswer((_) async {}); @@ -65,7 +65,7 @@ void main() { whenConstructor(verbose: false); final sshnpCore = - StubbedSshnpCore(atClient: mockAtClient, params: mockParams); + StubbedSshnp(atClient: mockAtClient, params: mockParams); /// Expect that the namespace is set in the preferences verify(() => mockAtClient.getPreferences()).called(1); @@ -81,7 +81,7 @@ void main() { whenConstructor(verbose: true); final sshnpCore = - StubbedSshnpCore(atClient: mockAtClient, params: mockParams); + StubbedSshnp(atClient: mockAtClient, params: mockParams); /// Expect that the namespace is set in the preferences verify(() => mockAtClient.getPreferences()).called(1); @@ -99,7 +99,7 @@ void main() { test('AsyncInitialization', () async { whenConstructor(); - final sshnpCore = StubbedSshnpCore( + final sshnpCore = StubbedSshnp( atClient: mockAtClient, params: mockParams, sshnpdChannel: mockSshnpdChannel, diff --git a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler_test.dart b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler_test.dart new file mode 100644 index 000000000..fca25aecf --- /dev/null +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_dart_ssh_key_handler_test.dart @@ -0,0 +1,25 @@ +import 'package:noports_core/sshnp_foundation.dart'; +import 'package:test/test.dart'; + +import 'sshnp_ssh_key_handler_mocks.dart'; + +void main() { + group('SshnpDartSshKeyHandler', () { + late MockSshnpDartSshKeyHandler keyHandler; + late MockAtSshKeyPair mockKeyPair; + + setUp(() { + keyHandler = MockSshnpDartSshKeyHandler(); + mockKeyPair = MockAtSshKeyPair(); + }); + + test('public API', () { + /// The Dart key handler requires that there is a setter for + /// identityKeyPair, in addition to a getter + keyHandler.identityKeyPair = mockKeyPair; + expect(keyHandler.identityKeyPair, mockKeyPair); + + expect(MockSshnpDartSshKeyHandler(), isA()); + }); // test initialization + }); // group SshnpDartKeyHandler +} diff --git a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart new file mode 100644 index 000000000..e94eeb352 --- /dev/null +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart @@ -0,0 +1,131 @@ +import 'package:at_client/at_client.dart'; +import 'package:noports_core/sshnp_foundation.dart'; +import 'package:test/test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../sshnp_mocks.dart'; +import 'sshnp_ssh_key_handler_mocks.dart'; + +void main() { + group('SshnpLocalSshKeyHandler', () { + late MockAtClient mockAtClient; + late MockSshnpParams mockParams; + late MockLocalSshKeyUtil keyUtil; + late MockAtSshKeyPair keyPair; + + late MockSshnpdChannel mockSshnpdChannel; + late MockSshrvdChannel mockSshrvdChannel; + + setUp(() { + mockAtClient = MockAtClient(); + mockParams = MockSshnpParams(); + keyUtil = MockLocalSshKeyUtil(); + keyPair = MockAtSshKeyPair(); + + mockSshnpdChannel = MockSshnpdChannel(); + mockSshrvdChannel = MockSshrvdChannel(); + registerFallbackValue(AtClientPreference()); + }); + + whenConstructor() { + when(() => mockParams.device).thenReturn('mydevice'); + when(() => mockParams.localPort).thenReturn(0); + when(() => mockParams.verbose).thenReturn(false); + when(() => mockAtClient.getPreferences()).thenReturn(null); + when(() => mockAtClient.setPreferences(any())).thenReturn(null); + } + + whenInitialization({AtSshKeyPair? identityKeyPair}) { + when(() => mockSshnpdChannel.callInitialization()) + .thenAnswer((_) async {}); + when(() => mockSshnpdChannel.resolveRemoteUsername()) + .thenAnswer((_) async => 'myRemoteUsername'); + when(() => mockSshnpdChannel.sharePublicKeyIfRequired(identityKeyPair)) + .thenAnswer((_) async {}); + when(() => mockSshrvdChannel.callInitialization()) + .thenAnswer((_) async {}); + } + + test('public API', () { + whenConstructor(); + final sshnp = StubbedSshnp( + atClient: mockAtClient, + params: mockParams, + ); + expect(sshnp, isA()); + }); // test public API + + test('initialization', () async { + whenConstructor(); + + final sshnp = StubbedSshnp( + atClient: mockAtClient, + params: mockParams, + sshKeyUtil: keyUtil, + sshnpdChannel: mockSshnpdChannel, + sshrvdChannel: mockSshrvdChannel, + ); + + whenInitialization(identityKeyPair: keyPair); + final identityFile = '.ssh/asdf'; + when(() => keyUtil.isValidPlatform).thenReturn(true); + when(() => mockParams.identityFile).thenReturn(identityFile); + when(() => keyUtil.getKeyPair(identifier: identityFile)) + .thenAnswer((_) async => keyPair); + + /// normally we would call [callInitialization()] but it's fine to call + /// initialize directly for testing purposes, since we avoid weird + /// lifecycle issues that could be caused by mocking + await sshnp.initialize(); + + /// We don't care about [SshnpCore] initialization here, we only care that + /// the [keyPair] is set correctly, since [SshnpCore] is tested elsewhere + expect(sshnp.identityKeyPair, keyPair); + }); // test initialization + + test('initialization - no identityFile', () async { + whenConstructor(); + + final sshnp = StubbedSshnp( + atClient: mockAtClient, + params: mockParams, + sshKeyUtil: keyUtil, + sshnpdChannel: mockSshnpdChannel, + sshrvdChannel: mockSshrvdChannel, + ); + + whenInitialization(identityKeyPair: keyPair); + when(() => keyUtil.isValidPlatform).thenReturn(true); + when(() => mockParams.identityFile).thenReturn(null); + when(() => mockSshnpdChannel.sharePublicKeyIfRequired(null)) + .thenAnswer((_) async {}); + when(() => keyUtil.getKeyPair(identifier: '.ssh/asdf')) + .thenAnswer((_) async => keyPair); + + /// normally we would call [callInitialization()] but it's fine to call + /// initialize directly for testing purposes, since we avoid weird + /// lifecycle issues that could be caused by mocking + await sshnp.initialize(); + + /// We don't care about [SshnpCore] initialization here, we only care that + /// the [keyPair] is set correctly, since [SshnpCore] is tested elsewhere + expect(sshnp.identityKeyPair, null); + }); // test initialization - no identityFile + + test('initialization - invalid platform', () { + whenConstructor(); + + final sshnp = StubbedSshnp( + atClient: mockAtClient, + params: mockParams, + sshKeyUtil: keyUtil, + sshnpdChannel: mockSshnpdChannel, + sshrvdChannel: mockSshrvdChannel, + ); + + whenInitialization(); + when(() => keyUtil.isValidPlatform).thenReturn(false); + expect(sshnp.initialize(), throwsA(isA())); + }); + }); // group SshnpLocalSshKeyHandler +} diff --git a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart index f4e11388d..0a75a76f1 100644 --- a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart @@ -6,3 +6,36 @@ class MockSshnpKeyHandler extends Mock with SshnpKeyHandler {} class MockAtSshKeyUtil extends Mock implements AtSshKeyUtil {} class MockAtSshKeyPair extends Mock implements AtSshKeyPair {} + +class MockSshnpDartSshKeyHandler extends Mock with SshnpDartSshKeyHandler {} + +class StubbedSshnp extends SshnpCore with SshnpLocalSshKeyHandler { + @override + LocalSshKeyUtil get keyUtil => _sshKeyUtil ?? (throw UnimplementedError()); + final LocalSshKeyUtil? _sshKeyUtil; + + StubbedSshnp({ + required super.atClient, + required super.params, + LocalSshKeyUtil? sshKeyUtil, + SshnpdChannel? sshnpdChannel, + SshrvdChannel? sshrvdChannel, + }) : _sshKeyUtil = sshKeyUtil, + _sshnpdChannel = sshnpdChannel, + _sshrvdChannel = sshrvdChannel; + + @override + Future run() => throw UnimplementedError(); + + @override + SshnpdChannel get sshnpdChannel => + _sshnpdChannel ?? (throw UnimplementedError()); + final SshnpdChannel? _sshnpdChannel; + + @override + SshrvdChannel get sshrvdChannel => + _sshrvdChannel ?? (throw UnimplementedError()); + final SshrvdChannel? _sshrvdChannel; +} + +class MockLocalSshKeyUtil extends Mock implements LocalSshKeyUtil {}