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; } 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; 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/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 +} 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 new file mode 100644 index 000000000..0a75a76f1 --- /dev/null +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_ssh_key_handler_mocks.dart @@ -0,0 +1,41 @@ +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 {} + +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 {} 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 +}