Skip to content

Commit

Permalink
feat: add URL reachability checks to shorebird doctor (#2537)
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanoltman authored Oct 15, 2024
1 parent 8f43bb5 commit cec1bf5
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/shorebird_cli/bin/shorebird.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:shorebird_cli/src/engine_config.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/network_checker.dart';
import 'package:shorebird_cli/src/os/os.dart';
import 'package:shorebird_cli/src/patch_diff_checker.dart';
import 'package:shorebird_cli/src/platform.dart';
Expand Down Expand Up @@ -63,6 +64,7 @@ Future<void> main(List<String> args) async {
iosRef,
javaRef,
loggerRef,
networkCheckerRef,
osInterfaceRef,
patchExecutableRef,
patchDiffCheckerRef,
Expand Down
6 changes: 6 additions & 0 deletions packages/shorebird_cli/lib/src/commands/doctor_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:shorebird_cli/src/android_studio.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/network_checker.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_flutter.dart';
Expand Down Expand Up @@ -94,6 +95,11 @@ Android Toolchain

logger.info(output.toString());

// ignore: cascade_invocations
logger.info('URL reachability:');
await networkChecker.checkReachability();
logger.info('');

await doctor.runValidators(doctor.generalValidators, applyFixes: shouldFix);

return ExitCode.success.code;
Expand Down
37 changes: 37 additions & 0 deletions packages/shorebird_cli/lib/src/network_checker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/logger.dart';

/// A reference to a [NetworkChecker] instance.
final networkCheckerRef = create(NetworkChecker.new);

/// The [NetworkChecker] instance available in the current zone.
NetworkChecker get networkChecker => read(networkCheckerRef);

/// {@template network_checker}
/// Checks reachability of various Shorebird-related endpoints and logs the
/// results.
/// {@endtemplate}
class NetworkChecker {
/// The URLs to check for network reachability.
static final urlsToCheck = [
'https://api.shorebird.dev',
'https://console.shorebird.dev',
'https://oauth2.googleapis.com',
].map(Uri.parse).toList();

/// Verify that each of [urlsToCheck] responds to an HTTP GET request.
Future<void> checkReachability() async {
for (final url in urlsToCheck) {
final progress = logger.progress('Checking reachability of $url');

try {
await httpClient.get(url);
progress.complete('$url OK');
} catch (e) {
progress.fail('$url unreachable');
logger.detail('Failed to reach $url: $e');
}
}
}
}
14 changes: 12 additions & 2 deletions packages/shorebird_cli/test/src/commands/doctor_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/network_checker.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_flutter.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
Expand All @@ -30,6 +31,7 @@ void main() {
late Doctor doctor;
late Gradlew gradlew;
late Java java;
late NetworkChecker networkChecker;
late ShorebirdLogger logger;
late ShorebirdEnv shorebirdEnv;
late ShorebirdFlutter shorebirdFlutter;
Expand All @@ -46,6 +48,7 @@ void main() {
gradlewRef.overrideWith(() => gradlew),
javaRef.overrideWith(() => java),
loggerRef.overrideWith(() => logger),
networkCheckerRef.overrideWith(() => networkChecker),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdFlutterRef.overrideWith(() => shorebirdFlutter),
},
Expand All @@ -61,6 +64,7 @@ void main() {
logsDirectory = Directory.systemTemp.createTempSync('shorebird_logs');
java = MockJava();
logger = MockShorebirdLogger();
networkChecker = MockNetworkChecker();
shorebirdEnv = MockShorebirdEnv();
shorebirdFlutter = MockShorebirdFlutter();
validator = MockValidator();
Expand All @@ -72,6 +76,9 @@ void main() {
when(() => androidSdk.adbPath).thenReturn(null);
when(() => gradlew.exists(any())).thenReturn(false);
when(() => java.home).thenReturn(null);
when(
() => networkChecker.checkReachability(),
).thenAnswer((_) async => {});
when(
() => shorebirdEnv.shorebirdEngineRevision,
).thenReturn(shorebirdEngineRevision);
Expand Down Expand Up @@ -104,8 +111,8 @@ Engine • revision $shorebirdEngineRevision
});

test(
'prints shorebird version, flutter revision, '
'flutter version, and engine revision', () async {
'''prints shorebird version, flutter revision, flutter version, and engine revision''',
() async {
const flutterVersion = '1.2.3';
when(
() => shorebirdFlutter.getVersionString(),
Expand All @@ -120,6 +127,7 @@ Flutter $flutterVersion • revision ${shorebirdEnv.flutterRevision}
Engine • revision $shorebirdEngineRevision
'''),
).called(1);
verify(() => networkChecker.checkReachability()).called(1);
});

group('--verbose', () {
Expand Down Expand Up @@ -197,6 +205,8 @@ Android Toolchain
''',
),
);

verify(() => networkChecker.checkReachability()).called(1);
});

group('when a gradlew executable exists', () {
Expand Down
3 changes: 3 additions & 0 deletions packages/shorebird_cli/test/src/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:shorebird_cli/src/engine_config.dart';
import 'package:shorebird_cli/src/executables/devicectl/apple_device.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/network_checker.dart';
import 'package:shorebird_cli/src/os/os.dart';
import 'package:shorebird_cli/src/patch_diff_checker.dart';
import 'package:shorebird_cli/src/platform/platform.dart';
Expand Down Expand Up @@ -112,6 +113,8 @@ class MockJwtHeader extends Mock implements JwtHeader {}

class MockJwtPayload extends Mock implements JwtPayload {}

class MockNetworkChecker extends Mock implements NetworkChecker {}

class MockOperatingSystemInterface extends Mock
implements OperatingSystemInterface {}

Expand Down
79 changes: 79 additions & 0 deletions packages/shorebird_cli/test/src/network_checker_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/network_checker.dart';
import 'package:test/test.dart';

import 'fakes.dart';
import 'mocks.dart';

void main() {
group(NetworkChecker, () {
late http.Client httpClient;
late ShorebirdLogger logger;
late Progress progress;
late NetworkChecker networkChecker;

R runWithOverrides<R>(R Function() body) {
return runScoped(
() => body(),
values: {
httpClientRef.overrideWith(() => httpClient),
loggerRef.overrideWith(() => logger),
},
);
}

setUpAll(() {
registerFallbackValue(FakeBaseRequest());
registerFallbackValue(Uri());
});

setUp(() {
httpClient = MockHttpClient();
logger = MockShorebirdLogger();
progress = MockProgress();

when(() => logger.progress(any())).thenReturn(progress);

networkChecker = NetworkChecker();
});

group('checkReachability', () {
group('when endpoints are reachable', () {
setUp(() {
when(() => httpClient.get(any())).thenAnswer(
(_) async => http.Response('', HttpStatus.ok),
);
});

test('logs reachability for each checked url', () async {
await runWithOverrides(networkChecker.checkReachability);

verify(
() => progress.complete(any(that: contains('OK'))),
).called(NetworkChecker.urlsToCheck.length);
});
});

group('when endpoints are not reachable', () {
setUp(() {
when(() => httpClient.send(any())).thenThrow(Exception('oops'));
});

test('logs reachability for each checked url', () async {
await runWithOverrides(networkChecker.checkReachability);

verify(
() => progress.fail(any(that: contains('unreachable'))),
).called(NetworkChecker.urlsToCheck.length);
});
});
});
});
}

0 comments on commit cec1bf5

Please sign in to comment.