-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(shorebird_cli): add
shorebird collaborators list
(#504)
- Loading branch information
Showing
5 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
packages/shorebird_cli/lib/src/commands/collaborators/collaborators.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export 'list_collaborators_command.dart'; |
19 changes: 19 additions & 0 deletions
19
packages/shorebird_cli/lib/src/commands/collaborators/collaborators_command.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import 'package:shorebird_cli/src/command.dart'; | ||
import 'package:shorebird_cli/src/commands/commands.dart'; | ||
|
||
/// {@template collaborators_command} | ||
/// `shorebird collaborators` | ||
/// Manage collaborators for a Shorebird app. | ||
/// {@endtemplate} | ||
class CollaboratorsCommand extends ShorebirdCommand { | ||
/// {@macro collaborators_command} | ||
CollaboratorsCommand({required super.logger}) { | ||
addSubcommand(ListCollaboratorsCommand(logger: logger)); | ||
} | ||
|
||
@override | ||
String get description => 'Manage collaborators for a Shorebird app'; | ||
|
||
@override | ||
String get name => 'collaborators'; | ||
} |
111 changes: 111 additions & 0 deletions
111
packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:barbecue/barbecue.dart'; | ||
import 'package:mason_logger/mason_logger.dart'; | ||
import 'package:shorebird_cli/src/auth_logger_mixin.dart'; | ||
import 'package:shorebird_cli/src/command.dart'; | ||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; | ||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; | ||
|
||
/// {@template list_collaborators_command} | ||
/// `shorebird collaborators list` | ||
/// List all collaborators for a Shorebird app. | ||
/// {@endtemplate} | ||
class ListCollaboratorsCommand extends ShorebirdCommand | ||
with AuthLoggerMixin, ShorebirdConfigMixin { | ||
/// {@macro list_collaborators_command} | ||
ListCollaboratorsCommand({ | ||
required super.logger, | ||
super.buildCodePushClient, | ||
super.auth, | ||
}) { | ||
argParser.addOption( | ||
_appIdOption, | ||
help: 'The app id to list collaborators for.', | ||
); | ||
} | ||
|
||
static const String _appIdOption = 'app-id'; | ||
|
||
@override | ||
String get name => 'list'; | ||
|
||
@override | ||
String get description => 'List all collaborators for a Shorebird app.'; | ||
|
||
@override | ||
List<String> get aliases => ['ls']; | ||
|
||
@override | ||
Future<int>? run() async { | ||
if (!auth.isAuthenticated) { | ||
printNeedsAuthInstructions(); | ||
return ExitCode.noUser.code; | ||
} | ||
|
||
final client = buildCodePushClient( | ||
httpClient: auth.client, | ||
hostedUri: hostedUri, | ||
); | ||
|
||
final appId = results[_appIdOption] as String? ?? getShorebirdYaml()?.appId; | ||
if (appId == null) { | ||
logger.err( | ||
''' | ||
Could not find an app id. | ||
You must either specify an app id via the "--$_appIdOption" flag or run this command from within a directory with a valid "shorebird.yaml" file.''', | ||
); | ||
return ExitCode.usage.code; | ||
} | ||
|
||
final List<Collaborator> collaborators; | ||
try { | ||
collaborators = await client.getCollaborators(appId: appId); | ||
} catch (error) { | ||
logger.err('$error'); | ||
return ExitCode.software.code; | ||
} | ||
|
||
logger.info( | ||
''' | ||
📱 App ID: ${lightCyan.wrap(appId)} | ||
🤝 Collaborators''', | ||
); | ||
|
||
if (collaborators.isEmpty) { | ||
logger.info('(empty)'); | ||
return ExitCode.success.code; | ||
} | ||
|
||
logger.info(collaborators.prettyPrint()); | ||
|
||
return ExitCode.success.code; | ||
} | ||
} | ||
|
||
extension on List<Collaborator> { | ||
String prettyPrint() { | ||
const cellStyle = CellStyle( | ||
paddingLeft: 1, | ||
paddingRight: 1, | ||
borderBottom: true, | ||
borderTop: true, | ||
borderLeft: true, | ||
borderRight: true, | ||
); | ||
return Table( | ||
cellStyle: cellStyle, | ||
header: const TableSection( | ||
rows: [ | ||
Row(cells: [Cell('Email')]) | ||
], | ||
), | ||
body: TableSection( | ||
rows: [ | ||
for (final collaborator in this) | ||
Row(cells: [Cell(collaborator.email)]), | ||
], | ||
), | ||
).render(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
packages/shorebird_cli/test/src/commands/collaborators/list_collaborators_command_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import 'package:args/args.dart'; | ||
import 'package:http/http.dart' as http; | ||
import 'package:mason_logger/mason_logger.dart'; | ||
import 'package:mocktail/mocktail.dart'; | ||
import 'package:shorebird_cli/src/auth/auth.dart'; | ||
import 'package:shorebird_cli/src/commands/commands.dart'; | ||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
class _MockArgResults extends Mock implements ArgResults {} | ||
|
||
class _MockHttpClient extends Mock implements http.Client {} | ||
|
||
class _MockAuth extends Mock implements Auth {} | ||
|
||
class _MockCodePushClient extends Mock implements CodePushClient {} | ||
|
||
class _MockLogger extends Mock implements Logger {} | ||
|
||
void main() { | ||
group('collborators list', () { | ||
const appId = 'test-app-id'; | ||
|
||
late ArgResults argResults; | ||
late http.Client httpClient; | ||
late Auth auth; | ||
late CodePushClient codePushClient; | ||
late Logger logger; | ||
late ListCollaboratorsCommand command; | ||
|
||
setUp(() { | ||
argResults = _MockArgResults(); | ||
httpClient = _MockHttpClient(); | ||
auth = _MockAuth(); | ||
codePushClient = _MockCodePushClient(); | ||
logger = _MockLogger(); | ||
command = ListCollaboratorsCommand( | ||
auth: auth, | ||
buildCodePushClient: ({ | ||
required http.Client httpClient, | ||
Uri? hostedUri, | ||
}) { | ||
return codePushClient; | ||
}, | ||
logger: logger, | ||
)..testArgResults = argResults; | ||
|
||
when(() => argResults['app-id']).thenReturn(appId); | ||
when(() => auth.isAuthenticated).thenReturn(true); | ||
when(() => auth.client).thenReturn(httpClient); | ||
}); | ||
|
||
test('name is correct', () { | ||
expect(command.name, equals('list')); | ||
}); | ||
|
||
test('description is correct', () { | ||
expect( | ||
command.description, | ||
equals('List all collaborators for a Shorebird app.'), | ||
); | ||
}); | ||
|
||
test('alias is correct', () { | ||
expect(command.aliases, equals(['ls'])); | ||
}); | ||
|
||
test('returns ExitCode.noUser when not logged in', () async { | ||
when(() => auth.isAuthenticated).thenReturn(false); | ||
expect(await command.run(), ExitCode.noUser.code); | ||
}); | ||
|
||
test('returns ExitCode.usage when app id is missing.', () async { | ||
when(() => argResults['app-id']).thenReturn(null); | ||
expect(await command.run(), ExitCode.usage.code); | ||
}); | ||
|
||
test('returns ExitCode.software when unable to get collaborators', | ||
() async { | ||
when( | ||
() => codePushClient.getCollaborators(appId: any(named: 'appId')), | ||
).thenThrow(Exception()); | ||
expect(await command.run(), ExitCode.software.code); | ||
}); | ||
|
||
test('returns ExitCode.success when collaborators are empty', () async { | ||
when( | ||
() => codePushClient.getCollaborators(appId: any(named: 'appId')), | ||
).thenAnswer((_) async => []); | ||
expect(await command.run(), ExitCode.success.code); | ||
verify(() => logger.info('(empty)')).called(1); | ||
}); | ||
|
||
test('returns ExitCode.success when collaborators are not empty', () async { | ||
final collaborators = [ | ||
const Collaborator(userId: 0, email: '[email protected]'), | ||
const Collaborator(userId: 1, email: '[email protected]'), | ||
]; | ||
when( | ||
() => codePushClient.getCollaborators(appId: any(named: 'appId')), | ||
).thenAnswer((_) async => collaborators); | ||
expect(await command.run(), ExitCode.success.code); | ||
verify( | ||
() => logger.info( | ||
''' | ||
📱 App ID: ${lightCyan.wrap(appId)} | ||
🤝 Collaborators''', | ||
), | ||
).called(1); | ||
verify( | ||
() => logger.info( | ||
''' | ||
┌────────────────────────┐ | ||
│ Email │ | ||
├────────────────────────┤ | ||
│ [email protected] │ | ||
├────────────────────────┤ | ||
│ [email protected] │ | ||
└────────────────────────┘''', | ||
), | ||
).called(1); | ||
}); | ||
}); | ||
} |