feat(shorebird_cli): add shorebird account usage command (#680)
This commit is contained in:
@@ -527,4 +527,18 @@ aar artifact already exists, continuing...''',
|
||||
|
||||
await promotePatch(patchId: patch.id, channel: channel);
|
||||
}
|
||||
|
||||
Future<List<AppUsage>> getUsage() async {
|
||||
final progress = logger.progress('Fetching usage');
|
||||
final List<AppUsage>? appsUsage;
|
||||
try {
|
||||
appsUsage = await codePushClient.getUsage();
|
||||
progress.complete();
|
||||
} catch (error) {
|
||||
progress.fail(error.toString());
|
||||
exit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
return appsUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'account_command.dart';
|
||||
export 'account_usage_command.dart';
|
||||
export 'create_account_command.dart';
|
||||
export 'subscribe_account_command.dart';
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
class AccountCommand extends ShorebirdCommand {
|
||||
/// {@macro account_command}
|
||||
AccountCommand() {
|
||||
addSubcommand(AccountUsageCommand());
|
||||
addSubcommand(CreateAccountCommand());
|
||||
addSubcommand(SubscribeAccountCommand());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:barbecue/barbecue.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_validation_mixin.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
/// {@template account_usage_command}
|
||||
/// `shorebird account usage`
|
||||
/// Get usage information for your Shorebird account.
|
||||
/// {@endtemplate}
|
||||
class AccountUsageCommand extends ShorebirdCommand
|
||||
with ShorebirdConfigMixin, ShorebirdValidationMixin {
|
||||
@override
|
||||
String get description => 'Get usage information for your Shorebird account.';
|
||||
|
||||
@override
|
||||
String get name => 'usage';
|
||||
|
||||
@override
|
||||
Future<int> run() async {
|
||||
try {
|
||||
await validatePreconditions(checkUserIsAuthenticated: true);
|
||||
} on PreconditionFailedException catch (e) {
|
||||
return e.exitCode.code;
|
||||
}
|
||||
|
||||
final usage = await codePushClientWrapper.getUsage();
|
||||
|
||||
logger
|
||||
..info('📈 Usage')
|
||||
..info(usage.prettyPrint());
|
||||
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
}
|
||||
|
||||
extension on List<AppUsage> {
|
||||
String prettyPrint() {
|
||||
const cellStyle = CellStyle(
|
||||
paddingLeft: 1,
|
||||
paddingRight: 1,
|
||||
borderBottom: true,
|
||||
borderTop: true,
|
||||
borderLeft: true,
|
||||
borderRight: true,
|
||||
);
|
||||
var totalPatchInstalls = 0;
|
||||
for (final appUsage in this) {
|
||||
for (final platformUsage in appUsage.platforms) {
|
||||
for (final archUsage in platformUsage.arches) {
|
||||
for (final patchUsage in archUsage.patches) {
|
||||
totalPatchInstalls += patchUsage.installCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Table(
|
||||
cellStyle: cellStyle,
|
||||
header: const TableSection(
|
||||
rows: [
|
||||
Row(cells: [Cell('Total Patch Installs')])
|
||||
],
|
||||
),
|
||||
body: TableSection(
|
||||
rows: [
|
||||
Row(cells: [Cell('$totalPatchInstalls')])
|
||||
],
|
||||
),
|
||||
).render();
|
||||
}
|
||||
}
|
||||
@@ -1557,6 +1557,63 @@ Please bump your version number and try again.''',
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getUsage', () {
|
||||
test('exits with code 70 when getUsage throws an exception', () async {
|
||||
when(() => codePushClient.getUsage()).thenThrow(Exception('oh no!'));
|
||||
|
||||
await expectLater(
|
||||
() => runWithOverrides(codePushClientWrapper.getUsage),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
|
||||
verify(() => progress.fail(any(that: contains('oh no!')))).called(1);
|
||||
});
|
||||
|
||||
test('returns usage when succeeds', () async {
|
||||
const usage = [
|
||||
AppUsage(
|
||||
id: 'test-app-id',
|
||||
platforms: [
|
||||
PlatformUsage(
|
||||
name: 'android',
|
||||
arches: [
|
||||
ArchUsage(
|
||||
name: 'aarch64',
|
||||
patches: [
|
||||
PatchUsage(id: 0, installCount: 10),
|
||||
PatchUsage(id: 1, installCount: 10)
|
||||
],
|
||||
),
|
||||
ArchUsage(
|
||||
name: 'arm',
|
||||
patches: [
|
||||
PatchUsage(id: 0, installCount: 10),
|
||||
PatchUsage(id: 1, installCount: 10)
|
||||
],
|
||||
),
|
||||
ArchUsage(
|
||||
name: 'x86',
|
||||
patches: [
|
||||
PatchUsage(id: 0, installCount: 1),
|
||||
PatchUsage(id: 1, installCount: 1)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
];
|
||||
when(() => codePushClient.getUsage()).thenAnswer((_) async => usage);
|
||||
|
||||
await expectLater(
|
||||
runWithOverrides(codePushClientWrapper.getUsage),
|
||||
completion(equals(usage)),
|
||||
);
|
||||
|
||||
verify(() => progress.complete()).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:scoped/scoped.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/account/account.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
|
||||
class _MockCodePushClientWrapper extends Mock
|
||||
implements CodePushClientWrapper {}
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockProgress extends Mock implements Progress {}
|
||||
|
||||
void main() {
|
||||
late Auth auth;
|
||||
late CodePushClientWrapper codePushClientWrapper;
|
||||
late Logger logger;
|
||||
late Progress progress;
|
||||
|
||||
late AccountUsageCommand command;
|
||||
|
||||
group(AccountUsageCommand, () {
|
||||
R runWithOverrides<R>(R Function() body) {
|
||||
return runScoped(
|
||||
body,
|
||||
values: {
|
||||
authRef.overrideWith(() => auth),
|
||||
loggerRef.overrideWith(() => logger),
|
||||
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
auth = _MockAuth();
|
||||
codePushClientWrapper = _MockCodePushClientWrapper();
|
||||
logger = _MockLogger();
|
||||
progress = _MockProgress();
|
||||
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
|
||||
command = runWithOverrides(AccountUsageCommand.new);
|
||||
});
|
||||
|
||||
test('has a description', () {
|
||||
expect(command.description, isNotEmpty);
|
||||
});
|
||||
|
||||
test('exits with code 67 when user is not logged in', () async {
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
|
||||
final result = await runWithOverrides(command.run);
|
||||
|
||||
expect(result, ExitCode.noUser.code);
|
||||
|
||||
verify(
|
||||
() => logger.err(any(that: contains('You must be logged in to run'))),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('exits with code 0 when usage is fetched.', () async {
|
||||
final usage = [
|
||||
const AppUsage(
|
||||
id: 'test-app-id',
|
||||
platforms: [
|
||||
PlatformUsage(
|
||||
name: 'android',
|
||||
arches: [
|
||||
ArchUsage(
|
||||
name: 'aarch64',
|
||||
patches: [
|
||||
PatchUsage(id: 0, installCount: 10),
|
||||
PatchUsage(id: 1, installCount: 10)
|
||||
],
|
||||
),
|
||||
ArchUsage(
|
||||
name: 'arm',
|
||||
patches: [
|
||||
PatchUsage(id: 0, installCount: 10),
|
||||
PatchUsage(id: 1, installCount: 10)
|
||||
],
|
||||
),
|
||||
ArchUsage(
|
||||
name: 'x86',
|
||||
patches: [
|
||||
PatchUsage(id: 0, installCount: 1),
|
||||
PatchUsage(id: 1, installCount: 1)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
];
|
||||
when(
|
||||
() => codePushClientWrapper.getUsage(),
|
||||
).thenAnswer((_) async => usage);
|
||||
|
||||
final result = await runWithOverrides(command.run);
|
||||
|
||||
expect(result, ExitCode.success.code);
|
||||
verify(() => logger.info('📈 Usage')).called(1);
|
||||
verify(
|
||||
() => logger.info('''
|
||||
┌──────────────────────┐
|
||||
│ Total Patch Installs │
|
||||
├──────────────────────┤
|
||||
│ 42 │
|
||||
└──────────────────────┘'''),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user