Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Mock tests for SshnpCore #565

Merged
merged 16 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
582 changes: 0 additions & 582 deletions docs/sshnp_class_diagrams.md

This file was deleted.

2 changes: 1 addition & 1 deletion packages/noports_core/lib/src/common/default_args.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ class DefaultArgs {
static const SupportedSshAlgorithm sshAlgorithm =
SupportedSshAlgorithm.ed25519;
static const bool verbose = false;
static const SupportedSshAlgorithm algorithm = SupportedSshAlgorithm.ed25519;
static const String rootDomain = 'root.atsign.org';
static const SshrvGenerator sshrvGenerator = Sshrv.exec;
static const int localSshdPort = 22;
static const int remoteSshdPort = 22;

/// value in seconds after which idle ssh tunnels will be closed
static const int idleTimeout = 15;
static const bool help = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mixin class AsyncInitialization {

/// To be called by the class that implements this mixin
/// to ensure that [initialize] is only called once
@visibleForTesting
@protected
Future<void> callInitialization() async {
if (!_initializeStarted) {
Expand All @@ -43,13 +44,15 @@ mixin class AsyncInitialization {
///
/// hint: call [isSafeToInitialize] at the beginning of this method
/// to ensure that initialization is not done more than once
@visibleForTesting
@visibleForOverriding
@protected
Future<void> initialize() async {}

/// To be called by the class that implements this mixin
/// to signal completion of the initialization process
/// hint: call this in the last line of [initialize]
@visibleForTesting
@protected
void completeInitialization() {
if (isSafeToInitialize) _initializedCompleter.complete();
Expand Down
21 changes: 7 additions & 14 deletions packages/noports_core/lib/src/sshnp/sshnp_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'dart:io';
import 'package:at_client/at_client.dart' hide StringBuffer;

import 'package:at_utils/at_logger.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:noports_core/src/common/mixins/async_completion.dart';
import 'package:noports_core/src/common/mixins/async_initialization.dart';
Expand All @@ -24,7 +23,7 @@ abstract class SshnpCore
// * AtClientBindings members
/// The logger for this class
@override
final AtSignLogger logger = AtSignLogger(' SshnpCore ');
final AtSignLogger logger = AtSignLogger('Sshnp');

/// The [AtClient] to use for this instance
@override
Expand Down Expand Up @@ -54,7 +53,7 @@ abstract class SshnpCore

/// The channel to communicate with the sshrvd (host)
@protected
SshrvdChannel? get sshrvdChannel;
SshrvdChannel get sshrvdChannel;

/// The channel to communicate with the sshnpd (daemon)
@protected
Expand All @@ -66,14 +65,7 @@ abstract class SshnpCore
}) : sessionId = Uuid().v4(),
namespace = '${params.device}.sshnp',
localPort = params.localPort {
/// Set the logger level to shout
logger.hierarchicalLoggingEnabled = true;
logger.logger.level = Level.SHOUT;

if (params.verbose) {
logger.logger.level = Level.INFO;
AtSignLogger.root_level = 'info';
}
logger.level = params.verbose ? 'info' : 'shout';

/// Set the namespace to the device's namespace
AtClientPreference preference =
Expand All @@ -95,21 +87,22 @@ abstract class SshnpCore
remoteUsername = await sshnpdChannel.resolveRemoteUsername();

/// Find a spare local port if required
await _findLocalPortIfRequired();
await findLocalPortIfRequired();

/// Shares the public key if required
await sshnpdChannel.sharePublicKeyIfRequired(identityKeyPair);

/// Retrieve the sshrvd host and port pair
await sshrvdChannel?.callInitialization();
await sshrvdChannel.callInitialization();
}

@override
Future<void> dispose() async {
completeDisposal();
}

Future<void> _findLocalPortIfRequired() async {
@visibleForTesting
Future<void> findLocalPortIfRequired() async {
// TODO investigate if this is a problem on mobile
// find a spare local port
if (localPort == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ mixin SshnpDartInitialTunnelHandler on SshnpCore
// If we are starting an initial tunnel, it should be to sshrvd,
// so it is safe to assume that sshrvdChannel is not null here
logger.info(
'Starting direct ssh session to ${sshrvdChannel!.host} on port ${sshrvdChannel!.sshrvdPort} with forwardLocal of $localPort');
'Starting direct ssh session to ${sshrvdChannel.host} on port ${sshrvdChannel.sshrvdPort} with forwardLocal of $localPort');
try {
late final SSHClient client;

late final SSHSocket socket;
try {
socket = await SSHSocket.connect(
sshrvdChannel!.host,
sshrvdChannel!.sshrvdPort!,
sshrvdChannel.host,
sshrvdChannel.sshrvdPort!,
).catchError((e) => throw e);
} catch (e, s) {
var error = SshnpError(
'Failed to open socket to ${sshrvdChannel!.host}:${sshrvdChannel!.sshrvdPort} : $e',
'Failed to open socket to ${sshrvdChannel.host}:${sshrvdChannel.sshrvdPort} : $e',
error: e,
stackTrace: s,
);
Expand All @@ -47,7 +47,7 @@ mixin SshnpDartInitialTunnelHandler on SshnpCore
);
} catch (e, s) {
throw SshnpError(
'Failed to create SSHClient for ${params.remoteUsername}@${sshrvdChannel!.host}:${sshrvdChannel!.sshrvdPort} : $e',
'Failed to create SSHClient for ${params.remoteUsername}@${sshrvdChannel.host}:${sshrvdChannel.sshrvdPort} : $e',
error: e,
stackTrace: s,
);
Expand All @@ -57,7 +57,7 @@ mixin SshnpDartInitialTunnelHandler on SshnpCore
await client.authenticated.catchError((e) => throw e);
} catch (e, s) {
throw SshnpError(
'Failed to authenticate as ${params.remoteUsername}@${sshrvdChannel!.host}:${sshrvdChannel!.sshrvdPort} : $e',
'Failed to authenticate as ${params.remoteUsername}@${sshrvdChannel.host}:${sshrvdChannel.sshrvdPort} : $e',
error: e,
stackTrace: s,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ mixin SshnpOpensshInitialTunnelHandler on SshnpCore
Process? process;
// If we are starting an initial tunnel, it should be to sshrvd,
// so it is safe to assume that sshrvdChannel is not null here
String argsString = '$remoteUsername@${sshrvdChannel!.host}'
' -p ${sshrvdChannel!.sshrvdPort}'
String argsString = '$remoteUsername@${sshrvdChannel.host}'
' -p ${sshrvdChannel.sshrvdPort}'
' -i $identifier'
' -L $localPort:localhost:${params.remoteSshdPort}'
' -o LogLevel=VERBOSE'
Expand Down
14 changes: 14 additions & 0 deletions packages/noports_core/test/sshnp/sshnp_core_constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class TestingKeyPair {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just generate at test time?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can open another PR which generates the temp keys later on, it was quicker for me to hard code.

static const public = 'ssh-ed25519 '
'AAAAC3NzaC1lZDI1NTE5AAAAIGoKAULs01V4Lqwdyz+IZLXkSfI+6HBJmFoAzCpRvoda '
'for-testing-do-not-use';

static const private = '''-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBqCgFC7NNVeC6sHcs/iGS15EnyPuhwSZhaAMwqUb6HWgAAAKBJvoc+Sb6H
PgAAAAtzc2gtZWQyNTUxOQAAACBqCgFC7NNVeC6sHcs/iGS15EnyPuhwSZhaAMwqUb6HWg
AAAEBnVg+o0CCLV0NqeXy6A1+w236zA8D6lqFe2mmyZbRqpWoKAULs01V4Lqwdyz+IZLXk
SfI+6HBJmFoAzCpRvodaAAAAFmZvci10ZXN0aW5nLWRvLW5vdC11c2UBAgMEBQYH
-----END OPENSSH PRIVATE KEY-----
''';
}
108 changes: 108 additions & 0 deletions packages/noports_core/test/sshnp/sshnp_core_mocks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import 'package:at_client/at_client.dart';
import 'package:mocktail/mocktail.dart';
import 'package:noports_core/sshnp_foundation.dart';

import 'sshnp_core_constants.dart';

/// Function Stubbing
abstract class FunctionCaller {
void call();
}

class FunctionStub extends Mock implements FunctionCaller {}

/// Mocked Classes
class MockAtClient extends Mock implements AtClient {}

class MockSshnpParams extends Mock implements SshnpParams {}

class MockSshnpdChannel extends Mock implements SshnpdChannel {}

class MockSshrvdChannel extends Mock implements SshrvdChannel {}

/// Stubbed [SshnpCore] (minimum viable implementation of [SshnpCore])
class StubbedSshnpCore extends SshnpCore with StubbedAsyncInitializationMixin {
FunctionStub? _stubbedFindLocalPortIfRequired;

void stubFindLocalPortIfRequired(
FunctionStub stubbedFindLocalPortIfRequired,
) {
_stubbedFindLocalPortIfRequired = stubbedFindLocalPortIfRequired;
}

StubbedSshnpCore({
required super.atClient,
required super.params,
SshnpdChannel? sshnpdChannel,
SshrvdChannel? sshrvdChannel,
}) : _sshnpdChannel = sshnpdChannel,
_sshrvdChannel = sshrvdChannel;

@override
Future<void> initialize() async {
await super.initialize();
completeInitialization();
}

@override
AtSshKeyPair? get identityKeyPair => _identityKeyPair;
final _identityKeyPair =
AtSshKeyPair.fromPem(TestingKeyPair.private, identifier: 'testing');

@override
AtSshKeyUtil get keyUtil => throw UnimplementedError();

@override
Future<SshnpResult> run() => throw UnimplementedError();

@override
SshnpdChannel get sshnpdChannel =>
_sshnpdChannel ?? (throw UnimplementedError());
final SshnpdChannel? _sshnpdChannel;

@override
SshrvdChannel get sshrvdChannel =>
_sshrvdChannel ?? (throw UnimplementedError());
final SshrvdChannel? _sshrvdChannel;

@override
Future<void> findLocalPortIfRequired() {
_stubbedFindLocalPortIfRequired?.call();
return super.findLocalPortIfRequired();
}
}

/// Stubbed mixin wrapper
mixin StubbedAsyncInitializationMixin on AsyncInitialization {
late FunctionStub _mockCallInitialization;
late FunctionStub _mockInitialize;
late FunctionStub _mockCompleteInitialization;

void stubAsyncInitialization({
required FunctionStub mockCallInitialization,
required FunctionStub mockInitialize,
required FunctionStub mockCompleteInitialization,
}) {
_mockCallInitialization = mockCallInitialization;
_mockInitialize = mockInitialize;
_mockCompleteInitialization = mockCompleteInitialization;
}

@override
Future<void> callInitialization() async {
_mockCallInitialization.call();
return super.callInitialization();
}

@override
Future<void> initialize() async {
_mockInitialize.call();
await super.initialize();
}

@override
void completeInitialization() {
_mockCompleteInitialization.call();
super.completeInitialization();
}
}
Loading