From c20190600c9df09851235e4f81608da4fd25b7d0 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Tue, 14 Mar 2023 12:07:29 -0500 Subject: [PATCH] refactor(shorebird_code_push_client): rename package and support getApps (#64) --- .github/workflows/main.yaml | 6 +- README.md | 12 +- packages/shorebird_cli/lib/src/command.dart | 7 +- .../lib/src/shorebird_engine_mixin.dart | 4 +- packages/shorebird_cli/pubspec.yaml | 4 +- .../test/src/commands/build_command_test.dart | 9 +- .../src/commands/publish_command_test.dart | 9 +- .../test/src/commands/run_command_test.dart | 9 +- .../shorebird_code_push_api_client/README.md | 27 -- .../lib/shorebird_code_push_api_client.dart | 4 - .../test/fixtures/release.txt | 0 .../shorebird_code_push_api_client_test.dart | 210 ----------- .../.gitignore | 0 packages/shorebird_code_push_client/README.md | 45 +++ .../analysis_options.yaml | 0 .../example/main.dart | 7 +- .../lib/shorebird_code_push_client.dart | 4 + .../lib/src/code_push_client.dart} | 28 +- .../pubspec.yaml | 4 +- .../test/src/code_push_client_test.dart | 353 ++++++++++++++++++ 20 files changed, 461 insertions(+), 281 deletions(-) delete mode 100644 packages/shorebird_code_push_api_client/README.md delete mode 100644 packages/shorebird_code_push_api_client/lib/shorebird_code_push_api_client.dart delete mode 100644 packages/shorebird_code_push_api_client/test/fixtures/release.txt delete mode 100644 packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart rename packages/{shorebird_code_push_api_client => shorebird_code_push_client}/.gitignore (100%) create mode 100644 packages/shorebird_code_push_client/README.md rename packages/{shorebird_code_push_api_client => shorebird_code_push_client}/analysis_options.yaml (100%) rename packages/{shorebird_code_push_api_client => shorebird_code_push_client}/example/main.dart (75%) create mode 100644 packages/shorebird_code_push_client/lib/shorebird_code_push_client.dart rename packages/{shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart => shorebird_code_push_client/lib/src/code_push_client.dart} (79%) rename packages/{shorebird_code_push_api_client => shorebird_code_push_client}/pubspec.yaml (76%) create mode 100644 packages/shorebird_code_push_client/test/src/code_push_client_test.dart diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index edb1b5c7..3ef3d30f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -34,10 +34,10 @@ jobs: shorebird_cli: - ./.github/actions/dart_package - packages/shorebird_cli/** - - packages/shorebird_code_push_api_client/** - shorebird_code_push_api_client: + - packages/shorebird_code_push_client/** + shorebird_code_push_client: - ./.github/actions/dart_package - - packages/shorebird_code_push_api_client/** + - packages/shorebird_code_push_client/** shorebird_code_push_protocol: - ./.github/actions/dart_package - packages/shorebird_code_push_protocol/** diff --git a/README.md b/README.md index e9006b10..88e4a0b9 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,12 @@ Refer to [shorebird/install](https://github.com/shorebirdtech/install) for insta This repository is a monorepo containing the following packages: -| Package | Description | -| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| [shorebird_cli](packages/shorebird_cli/README.md) | Command-line which allows developers to interact with various Shorebird services | -| [shorebird_code_push_api_client](packages/shorebird_code_push_api_client/README.md) | Dart library which allows Dart applications to interact with the ShoreBird CodePush API | -| [shorebird_code_push_protocol](packages/shorebird_code_push_protocol/README.md) | Dart library which contains common interfaces used by Shorebird CodePush | -| [shorebird_code_push_updater](updater/README.md) | Rust library which handles the CodePush logic and does the real update work | +| Package | Description | +| ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| [shorebird_cli](packages/shorebird_cli/README.md) | Command-line which allows developers to interact with various Shorebird services | +| [shorebird_code_push_client](packages/shorebird_code_push_client/README.md) | Dart library which allows Dart applications to interact with the ShoreBird CodePush API | +| [shorebird_code_push_protocol](packages/shorebird_code_push_protocol/README.md) | Dart library which contains common interfaces used by Shorebird CodePush | +| [shorebird_code_push_updater](updater/README.md) | Rust library which handles the CodePush logic and does the real update work | For more information, please refer to the documentation for each package. diff --git a/packages/shorebird_cli/lib/src/command.dart b/packages/shorebird_cli/lib/src/command.dart index 1b24eebc..94de0971 100644 --- a/packages/shorebird_cli/lib/src/command.dart +++ b/packages/shorebird_cli/lib/src/command.dart @@ -5,10 +5,10 @@ import 'package:args/command_runner.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:meta/meta.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; -import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart'; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:uuid/uuid.dart'; -typedef CodePushClientBuilder = ShorebirdCodePushApiClient Function({ +typedef CodePushClientBuilder = CodePushClient Function({ required String apiKey, }); @@ -35,8 +35,7 @@ abstract class ShorebirdCommand extends Command { StartProcess? startProcess, UuidBuilder? buildUuid, }) : auth = auth ?? Auth(), - buildCodePushClient = - buildCodePushClient ?? ShorebirdCodePushApiClient.new, + buildCodePushClient = buildCodePushClient ?? CodePushClient.new, runProcess = runProcess ?? Process.run, startProcess = startProcess ?? Process.start, buildUuid = buildUuid ?? const Uuid().v4; diff --git a/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart index b56c093a..6d925bc3 100644 --- a/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart @@ -5,7 +5,7 @@ import 'dart:isolate'; import 'package:archive/archive_io.dart'; import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/command.dart'; -import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart'; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; mixin ShorebirdEngineMixin on ShorebirdCommand { String get shorebirdEnginePath { @@ -60,7 +60,7 @@ mixin ShorebirdEngineMixin on ShorebirdCommand { } Future _downloadShorebirdEngine( - ShorebirdCodePushApiClient codePushClient, + CodePushClient codePushClient, String path, ) async { final engine = await codePushClient.downloadEngine('latest'); diff --git a/packages/shorebird_cli/pubspec.yaml b/packages/shorebird_cli/pubspec.yaml index 55d1c5d4..11c7d62f 100644 --- a/packages/shorebird_cli/pubspec.yaml +++ b/packages/shorebird_cli/pubspec.yaml @@ -20,8 +20,8 @@ dependencies: path: ^1.8.3 pub_updater: ^0.2.4 pubspec_parse: ^1.2.2 - shorebird_code_push_api_client: - path: ../shorebird_code_push_api_client + shorebird_code_push_client: + path: ../shorebird_code_push_client uuid: ^3.0.7 yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/packages/shorebird_cli/test/src/commands/build_command_test.dart b/packages/shorebird_cli/test/src/commands/build_command_test.dart index 0998474b..cfb0d268 100644 --- a/packages/shorebird_cli/test/src/commands/build_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build_command_test.dart @@ -6,7 +6,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/auth/session.dart'; import 'package:shorebird_cli/src/commands/build_command.dart'; -import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart'; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; class _MockAuth extends Mock implements Auth {} @@ -17,8 +17,7 @@ class _MockProgress extends Mock implements Progress {} class _MockProcessResult extends Mock implements ProcessResult {} -class _MockShorebirdCodePushApiClient extends Mock - implements ShorebirdCodePushApiClient {} +class _MockCodePushClient extends Mock implements CodePushClient {} void main() { group('build', () { @@ -28,14 +27,14 @@ void main() { ); late Auth auth; - late ShorebirdCodePushApiClient codePushClient; + late CodePushClient codePushClient; late Logger logger; late ProcessResult processResult; late BuildCommand buildCommand; setUp(() { auth = _MockAuth(); - codePushClient = _MockShorebirdCodePushApiClient(); + codePushClient = _MockCodePushClient(); logger = _MockLogger(); processResult = _MockProcessResult(); buildCommand = BuildCommand( diff --git a/packages/shorebird_cli/test/src/commands/publish_command_test.dart b/packages/shorebird_cli/test/src/commands/publish_command_test.dart index 465e7c83..8d868fe2 100644 --- a/packages/shorebird_cli/test/src/commands/publish_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/publish_command_test.dart @@ -8,7 +8,7 @@ import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/auth/session.dart'; import 'package:shorebird_cli/src/commands/publish_command.dart'; -import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart'; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; class _MockArgResults extends Mock implements ArgResults {} @@ -19,8 +19,7 @@ class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} -class _MockShorebirdCodePushApiClient extends Mock - implements ShorebirdCodePushApiClient {} +class _MockCodePushClient extends Mock implements CodePushClient {} class _FakeCommandRunner extends Fake implements CommandRunner { @override @@ -44,14 +43,14 @@ environment: late ArgResults argResults; late Auth auth; late Logger logger; - late _MockShorebirdCodePushApiClient codePushClient; + late CodePushClient codePushClient; late PublishCommand command; setUp(() { argResults = _MockArgResults(); auth = _MockAuth(); logger = _MockLogger(); - codePushClient = _MockShorebirdCodePushApiClient(); + codePushClient = _MockCodePushClient(); command = PublishCommand( auth: auth, buildCodePushClient: ({required String apiKey}) => codePushClient, diff --git a/packages/shorebird_cli/test/src/commands/run_command_test.dart b/packages/shorebird_cli/test/src/commands/run_command_test.dart index 6589919c..3ea2cbcd 100644 --- a/packages/shorebird_cli/test/src/commands/run_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/run_command_test.dart @@ -11,7 +11,7 @@ import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/auth/session.dart'; import 'package:shorebird_cli/src/commands/run_command.dart'; -import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart'; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; class _MockArgResults extends Mock implements ArgResults {} @@ -24,8 +24,7 @@ class _MockProgress extends Mock implements Progress {} class _MockProcess extends Mock implements Process {} -class _MockShorebirdCodePushApiClient extends Mock - implements ShorebirdCodePushApiClient {} +class _MockCodePushClient extends Mock implements CodePushClient {} void main() { group('run', () { @@ -38,7 +37,7 @@ void main() { late Auth auth; late Logger logger; late Process process; - late ShorebirdCodePushApiClient codePushClient; + late CodePushClient codePushClient; late RunCommand runCommand; setUp(() { @@ -46,7 +45,7 @@ void main() { auth = _MockAuth(); logger = _MockLogger(); process = _MockProcess(); - codePushClient = _MockShorebirdCodePushApiClient(); + codePushClient = _MockCodePushClient(); runCommand = RunCommand( auth: auth, logger: logger, diff --git a/packages/shorebird_code_push_api_client/README.md b/packages/shorebird_code_push_api_client/README.md deleted file mode 100644 index daf6c145..00000000 --- a/packages/shorebird_code_push_api_client/README.md +++ /dev/null @@ -1,27 +0,0 @@ -## Shorebird CodePush API Client - -The Shorebird CodePush API Client is a Dart library which allows Dart applications to interact with the ShoreBird CodePush API. - -### Installing - -To get started, add the library to your `pubspec.yaml`: - -```yaml -dependencies: - shorebird_code_push_api_client: - git: - url: https://github.com/shorebirdtech/shorebird - path: packages/shorebird_code_push_api_client -``` - -### Usage - -```dart -void main() async { - // Create an instance of the client. - final client = ShorebirdCodePushApiClient(); - - // Publish a new release. - await client.createRelease('path/to/release'); -} -``` diff --git a/packages/shorebird_code_push_api_client/lib/shorebird_code_push_api_client.dart b/packages/shorebird_code_push_api_client/lib/shorebird_code_push_api_client.dart deleted file mode 100644 index f20b77ac..00000000 --- a/packages/shorebird_code_push_api_client/lib/shorebird_code_push_api_client.dart +++ /dev/null @@ -1,4 +0,0 @@ -/// The Shorebird CodePush API Client -library shorebird_code_push_api_client; - -export 'src/shorebird_code_push_api_client.dart'; diff --git a/packages/shorebird_code_push_api_client/test/fixtures/release.txt b/packages/shorebird_code_push_api_client/test/fixtures/release.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart b/packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart deleted file mode 100644 index a798bc9d..00000000 --- a/packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart +++ /dev/null @@ -1,210 +0,0 @@ -// ignore_for_file: prefer_const_constructors -import 'dart:io'; - -import 'package:http/http.dart' as http; -import 'package:mocktail/mocktail.dart'; -import 'package:path/path.dart' as path; -import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart'; -import 'package:test/test.dart'; - -class _MockHttpClient extends Mock implements http.Client {} - -class _FakeBaseRequest extends Fake implements http.BaseRequest {} - -void main() { - group('ShorebirdCodePushApiClient', () { - const apiKey = 'api-key'; - const productId = 'shorebird-example'; - - late http.Client httpClient; - late ShorebirdCodePushApiClient shorebirdCodePushApiClient; - - setUpAll(() { - registerFallbackValue(_FakeBaseRequest()); - registerFallbackValue(Uri()); - }); - - setUp(() { - httpClient = _MockHttpClient(); - shorebirdCodePushApiClient = ShorebirdCodePushApiClient( - apiKey: apiKey, - httpClient: httpClient, - ); - }); - - test('can be instantiated', () { - expect(ShorebirdCodePushApiClient(apiKey: apiKey), isNotNull); - }); - - group('createApp', () { - test('throws an exception if the http request fails', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); - - expect( - shorebirdCodePushApiClient.createApp(productId: productId), - throwsA(isA()), - ); - }); - - test('completes when request succeeds', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer((_) async => http.Response('', HttpStatus.created)); - - await shorebirdCodePushApiClient.createApp(productId: productId); - - final uri = verify( - () => httpClient.post( - captureAny(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).captured.single as Uri; - - expect( - uri, - shorebirdCodePushApiClient.hostedUri.replace(path: '/api/v1/apps'), - ); - }); - }); - - group('createPatch', () { - test('throws an exception if the http request fails', () async { - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.empty(), - 400, - ); - }); - - expect( - shorebirdCodePushApiClient.createPatch( - artifactPath: path.join('test', 'fixtures', 'release.txt'), - baseVersion: '1.0.0', - productId: 'shorebird-example', - channel: 'stable', - ), - throwsA(isA()), - ); - }); - - test('sends a multipart request to the correct url', () async { - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.empty(), - HttpStatus.created, - ); - }); - - await shorebirdCodePushApiClient.createPatch( - artifactPath: path.join('test', 'fixtures', 'release.txt'), - baseVersion: '1.0.0', - productId: 'shorebird-example', - channel: 'stable', - ); - - final request = verify(() => httpClient.send(captureAny())) - .captured - .single as http.MultipartRequest; - expect( - request.url, - shorebirdCodePushApiClient.hostedUri.replace(path: '/api/v1/patches'), - ); - }); - }); - - group('deleteApp', () { - test('throws an exception if the http request fails', () async { - when( - () => httpClient.delete(any(), headers: any(named: 'headers')), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); - - expect( - shorebirdCodePushApiClient.deleteApp(productId: productId), - throwsA(isA()), - ); - }); - - test('completes when request succeeds', () async { - when( - () => httpClient.delete( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer((_) async => http.Response('', HttpStatus.noContent)); - - await shorebirdCodePushApiClient.deleteApp(productId: productId); - - final uri = verify( - () => httpClient.delete( - captureAny(), - headers: any(named: 'headers'), - ), - ).captured.single as Uri; - - expect( - uri, - shorebirdCodePushApiClient.hostedUri.replace( - path: '/api/v1/apps/$productId', - ), - ); - }); - }); - - group('downloadEngine', () { - const engineRevision = 'engine-revision'; - test('throws an exception if the http request fails', () async { - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.empty(), - 400, - ); - }); - - expect( - shorebirdCodePushApiClient.downloadEngine(engineRevision), - throwsA(isA()), - ); - }); - - test('sends a request to the correct url', () async { - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.empty(), - HttpStatus.ok, - ); - }); - - await shorebirdCodePushApiClient.downloadEngine(engineRevision); - - final request = verify(() => httpClient.send(captureAny())) - .captured - .single as http.Request; - - expect( - request.url, - Uri.parse( - 'https://storage.googleapis.com/code-push-dev.appspot.com/engines/dev/engine.zip', - ), - ); - }); - }); - - group('close', () { - test('closes the underlying client', () { - shorebirdCodePushApiClient.close(); - verify(() => httpClient.close()).called(1); - }); - }); - }); -} diff --git a/packages/shorebird_code_push_api_client/.gitignore b/packages/shorebird_code_push_client/.gitignore similarity index 100% rename from packages/shorebird_code_push_api_client/.gitignore rename to packages/shorebird_code_push_client/.gitignore diff --git a/packages/shorebird_code_push_client/README.md b/packages/shorebird_code_push_client/README.md new file mode 100644 index 00000000..9e6b77c7 --- /dev/null +++ b/packages/shorebird_code_push_client/README.md @@ -0,0 +1,45 @@ +## Shorebird CodePush Client + +The Shorebird CodePush Client is a Dart library which allows Dart applications to interact with the ShoreBird CodePush API. + +### Installing + +To get started, add the library to your `pubspec.yaml`: + +```yaml +dependencies: + shorebird_code_push_client: + git: + url: https://github.com/shorebirdtech/shorebird + path: packages/shorebird_code_push_client +``` + +### Usage + +```dart +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; + +Future main() async { + final client = CodePushClient(apiKey: ''); + + // Download the latest engine revision. + final engine = await client.downloadEngine('latest'); + + // Create a new Shorebird application. + await client.createApp(productId: ''); + + // List all apps. + final apps = await client.getApps(); + + // Create a new patch. + await client.createPatch( + artifactPath: '', // e.g. 'libapp.so' + baseVersion: '', // e.g. '1.0.0' + productId: '', // e.g. 'shorebird-example' + channel: '', // e.g. 'stable' + ); + + // Close the client. + client.close(); +} +``` diff --git a/packages/shorebird_code_push_api_client/analysis_options.yaml b/packages/shorebird_code_push_client/analysis_options.yaml similarity index 100% rename from packages/shorebird_code_push_api_client/analysis_options.yaml rename to packages/shorebird_code_push_client/analysis_options.yaml diff --git a/packages/shorebird_code_push_api_client/example/main.dart b/packages/shorebird_code_push_client/example/main.dart similarity index 75% rename from packages/shorebird_code_push_api_client/example/main.dart rename to packages/shorebird_code_push_client/example/main.dart index 5549b965..1f1eff02 100644 --- a/packages/shorebird_code_push_api_client/example/main.dart +++ b/packages/shorebird_code_push_client/example/main.dart @@ -1,9 +1,9 @@ // ignore_for_file: unused_local_variable -import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart'; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; Future main() async { - final client = ShorebirdCodePushApiClient(apiKey: ''); + final client = CodePushClient(apiKey: ''); // Download the latest engine revision. final engine = await client.downloadEngine('latest'); @@ -11,6 +11,9 @@ Future main() async { // Create a new Shorebird application. await client.createApp(productId: ''); + // List all apps. + final apps = await client.getApps(); + // Create a new patch. await client.createPatch( artifactPath: '', // e.g. 'libapp.so' diff --git a/packages/shorebird_code_push_client/lib/shorebird_code_push_client.dart b/packages/shorebird_code_push_client/lib/shorebird_code_push_client.dart new file mode 100644 index 00000000..ec017144 --- /dev/null +++ b/packages/shorebird_code_push_client/lib/shorebird_code_push_client.dart @@ -0,0 +1,4 @@ +/// The Shorebird CodePush API Client +library shorebird_code_push_client; + +export 'src/code_push_client.dart'; diff --git a/packages/shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart b/packages/shorebird_code_push_client/lib/src/code_push_client.dart similarity index 79% rename from packages/shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart rename to packages/shorebird_code_push_client/lib/src/code_push_client.dart index 6adca31b..f4950f7f 100644 --- a/packages/shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart +++ b/packages/shorebird_code_push_client/lib/src/code_push_client.dart @@ -3,13 +3,14 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:http/http.dart' as http; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; -/// {@template shorebird_code_push_api_client} -/// The Shorebird CodePush API Client +/// {@template code_push_client} +/// Dart client for the Shorebird CodePush API. /// {@endtemplate} -class ShorebirdCodePushApiClient { - /// {@macro shorebird_code_push_api_client} - ShorebirdCodePushApiClient({ +class CodePushClient { + /// {@macro code_push_client} + CodePushClient({ required String apiKey, http.Client? httpClient, Uri? hostedUri, @@ -97,6 +98,23 @@ class ShorebirdCodePushApiClient { return response.stream.toBytes(); } + /// List all apps for the current account. + Future> getApps() async { + final response = await _httpClient.get( + Uri.parse('$hostedUri/api/v1/apps'), + headers: _apiKeyHeader, + ); + + if (response.statusCode != HttpStatus.ok) { + throw Exception('${response.statusCode} ${response.reasonPhrase}'); + } + + final apps = json.decode(response.body) as List; + return apps + .map((app) => App.fromJson(app as Map)) + .toList(); + } + /// Closes the client. void close() => _httpClient.close(); } diff --git a/packages/shorebird_code_push_api_client/pubspec.yaml b/packages/shorebird_code_push_client/pubspec.yaml similarity index 76% rename from packages/shorebird_code_push_api_client/pubspec.yaml rename to packages/shorebird_code_push_client/pubspec.yaml index b8c38a0d..f93c4475 100644 --- a/packages/shorebird_code_push_api_client/pubspec.yaml +++ b/packages/shorebird_code_push_client/pubspec.yaml @@ -1,4 +1,4 @@ -name: shorebird_code_push_api_client +name: shorebird_code_push_client description: Library which allows Dart applications to interact with the ShoreBird CodePush API version: 0.1.0+1 repository: https://github.com/shorebirdtech/shorebird @@ -10,6 +10,8 @@ environment: dependencies: http: ^0.13.5 + shorebird_code_push_protocol: + path: ../shorebird_code_push_protocol dev_dependencies: mocktail: ^0.3.0 diff --git a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart new file mode 100644 index 00000000..ca514b95 --- /dev/null +++ b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart @@ -0,0 +1,353 @@ +// ignore_for_file: prefer_const_constructors +import 'dart:convert'; +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as path; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +class _MockHttpClient extends Mock implements http.Client {} + +class _FakeBaseRequest extends Fake implements http.BaseRequest {} + +void main() { + group('CodePushClient', () { + const apiKey = 'api-key'; + const productId = 'shorebird-example'; + + late http.Client httpClient; + late CodePushClient codePushClient; + + setUpAll(() { + registerFallbackValue(_FakeBaseRequest()); + registerFallbackValue(Uri()); + }); + + setUp(() { + httpClient = _MockHttpClient(); + codePushClient = CodePushClient( + apiKey: apiKey, + httpClient: httpClient, + ); + }); + + test('can be instantiated', () { + expect(CodePushClient(apiKey: apiKey), isNotNull); + }); + + group('createApp', () { + test('throws an exception if the http request fails', () async { + when( + () => httpClient.post( + any(), + headers: any(named: 'headers'), + body: any(named: 'body'), + ), + ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + + expect( + codePushClient.createApp(productId: productId), + throwsA(isA()), + ); + }); + + test('completes when request succeeds', () async { + when( + () => httpClient.post( + any(), + headers: any(named: 'headers'), + body: any(named: 'body'), + ), + ).thenAnswer((_) async => http.Response('', HttpStatus.created)); + + await codePushClient.createApp(productId: productId); + + final uri = verify( + () => httpClient.post( + captureAny(), + headers: any(named: 'headers'), + body: any(named: 'body'), + ), + ).captured.single as Uri; + + expect( + uri, + codePushClient.hostedUri.replace(path: '/api/v1/apps'), + ); + }); + }); + + group('createPatch', () { + test('throws an exception if the http request fails', () async { + final tempDir = Directory.systemTemp.createTempSync(); + final fixture = File(path.join(tempDir.path, 'release.txt')) + ..createSync(); + + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + Stream.empty(), + 400, + ); + }); + + expect( + codePushClient.createPatch( + artifactPath: fixture.path, + baseVersion: '1.0.0', + productId: 'shorebird-example', + channel: 'stable', + ), + throwsA(isA()), + ); + }); + + test('sends a multipart request to the correct url', () async { + final tempDir = Directory.systemTemp.createTempSync(); + final fixture = File(path.join(tempDir.path, 'release.txt')) + ..createSync(); + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + Stream.empty(), + HttpStatus.created, + ); + }); + + await codePushClient.createPatch( + artifactPath: fixture.path, + baseVersion: '1.0.0', + productId: 'shorebird-example', + channel: 'stable', + ); + + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.MultipartRequest; + + expect( + request.url, + codePushClient.hostedUri.replace(path: '/api/v1/patches'), + ); + }); + }); + + group('deleteApp', () { + test('throws an exception if the http request fails', () async { + when( + () => httpClient.delete(any(), headers: any(named: 'headers')), + ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + + expect( + codePushClient.deleteApp(productId: productId), + throwsA(isA()), + ); + }); + + test('completes when request succeeds', () async { + when( + () => httpClient.delete( + any(), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => http.Response('', HttpStatus.noContent)); + + await codePushClient.deleteApp(productId: productId); + + final uri = verify( + () => httpClient.delete( + captureAny(), + headers: any(named: 'headers'), + ), + ).captured.single as Uri; + + expect( + uri, + codePushClient.hostedUri.replace( + path: '/api/v1/apps/$productId', + ), + ); + }); + }); + + group('downloadEngine', () { + const engineRevision = 'engine-revision'; + test('throws an exception if the http request fails', () async { + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + Stream.empty(), + 400, + ); + }); + + expect( + codePushClient.downloadEngine(engineRevision), + throwsA(isA()), + ); + }); + + test('sends a request to the correct url', () async { + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + Stream.empty(), + HttpStatus.ok, + ); + }); + + await codePushClient.downloadEngine(engineRevision); + + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.Request; + + expect( + request.url, + Uri.parse( + 'https://storage.googleapis.com/code-push-dev.appspot.com/engines/dev/engine.zip', + ), + ); + }); + }); + + group('getApps', () { + test('throws an exception if the http request fails', () async { + when( + () => httpClient.get( + any(), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + + expect( + codePushClient.getApps(), + throwsA(isA()), + ); + }); + + test('completes when request succeeds (empty)', () async { + when( + () => httpClient.get( + any(), + headers: any(named: 'headers'), + ), + ).thenAnswer( + (_) async => http.Response(json.encode([]), HttpStatus.ok), + ); + + final apps = await codePushClient.getApps(); + expect(apps, isEmpty); + }); + + test('completes when request succeeds (populated)', () async { + final expected = [ + App( + productId: 'shorebird-example', + releases: [ + Release( + version: '1.0.0', + patches: [ + Patch( + number: 1, + channels: ['stable'], + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'http://localhost:8080', + hash: '#', + ) + ], + ), + Patch( + number: 2, + channels: ['stable', 'dev'], + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'http://localhost:8080', + hash: '#', + ) + ], + ) + ], + ), + Release(version: '2.0.0'), + ], + ), + App( + productId: 'shorebird-counter', + releases: [ + Release( + version: '1.0.0', + patches: [ + Patch( + number: 1, + channels: ['stable'], + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'http://localhost:8080', + hash: '#', + ) + ], + ), + Patch( + number: 2, + channels: ['stable', 'dev'], + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'http://localhost:8080', + hash: '#', + ) + ], + ) + ], + ), + Release( + version: '1.0.1', + patches: [ + Patch( + number: 1, + channels: ['stable'], + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'http://localhost:8080', + hash: '#', + ) + ], + ), + ], + ), + ], + ), + ]; + + when( + () => httpClient.get( + any(), + headers: any(named: 'headers'), + ), + ).thenAnswer( + (_) async => http.Response(json.encode(expected), HttpStatus.ok), + ); + + final actual = await codePushClient.getApps(); + expect(json.encode(actual), equals(json.encode(expected))); + }); + }); + + group('close', () { + test('closes the underlying client', () { + codePushClient.close(); + verify(() => httpClient.close()).called(1); + }); + }); + }); +}