refactor(shorebird_code_push_client): rename package and support getApps (#64)

This commit is contained in:
Felix Angelov
2023-03-14 12:07:29 -05:00
committed by GitHub
parent 25231b36c7
commit c20190600c
20 changed files with 461 additions and 281 deletions
+3 -3
View File
@@ -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/**
+6 -6
View File
@@ -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.
+3 -4
View File
@@ -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<int> {
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;
@@ -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<void> _downloadShorebirdEngine(
ShorebirdCodePushApiClient codePushClient,
CodePushClient codePushClient,
String path,
) async {
final engine = await codePushClient.downloadEngine('latest');
+2 -2
View File
@@ -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
@@ -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(
@@ -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<int> {
@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,
@@ -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,
@@ -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');
}
```
@@ -1,4 +0,0 @@
/// The Shorebird CodePush API Client
library shorebird_code_push_api_client;
export 'src/shorebird_code_push_api_client.dart';
@@ -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<Exception>()),
);
});
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<Exception>()),
);
});
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<Exception>()),
);
});
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<Exception>()),
);
});
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);
});
});
});
}
@@ -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<void> main() async {
final client = CodePushClient(apiKey: '<API KEY>');
// Download the latest engine revision.
final engine = await client.downloadEngine('latest');
// Create a new Shorebird application.
await client.createApp(productId: '<PRODUCT ID>');
// List all apps.
final apps = await client.getApps();
// Create a new patch.
await client.createPatch(
artifactPath: '<PATH TO ARTIFACT>', // e.g. 'libapp.so'
baseVersion: '<BASE VERSION>', // e.g. '1.0.0'
productId: '<PRODUCT ID>', // e.g. 'shorebird-example'
channel: '<CHANNEL>', // e.g. 'stable'
);
// Close the client.
client.close();
}
```
@@ -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<void> main() async {
final client = ShorebirdCodePushApiClient(apiKey: '<API KEY>');
final client = CodePushClient(apiKey: '<API KEY>');
// Download the latest engine revision.
final engine = await client.downloadEngine('latest');
@@ -11,6 +11,9 @@ Future<void> main() async {
// Create a new Shorebird application.
await client.createApp(productId: '<PRODUCT ID>');
// List all apps.
final apps = await client.getApps();
// Create a new patch.
await client.createPatch(
artifactPath: '<PATH TO ARTIFACT>', // e.g. 'libapp.so'
@@ -0,0 +1,4 @@
/// The Shorebird CodePush API Client
library shorebird_code_push_client;
export '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<List<App>> 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<String, dynamic>))
.toList();
}
/// Closes the client.
void close() => _httpClient.close();
}
@@ -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
@@ -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<Exception>()),
);
});
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<Exception>()),
);
});
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<Exception>()),
);
});
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<Exception>()),
);
});
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<Exception>()),
);
});
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);
});
});
});
}