feat(shorebird_cli): expose user (#224)
This commit is contained in:
@@ -4,9 +4,11 @@ import 'dart:io';
|
||||
import 'package:googleapis_auth/auth_io.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/auth/jwt.dart';
|
||||
import 'package:shorebird_cli/src/auth/models/models.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
|
||||
export 'package:googleapis_auth/googleapis_auth.dart' show AccessCredentials;
|
||||
export 'package:shorebird_cli/src/auth/models/models.dart' show User;
|
||||
|
||||
final _clientId = ClientId(
|
||||
/// Shorebird CLI's OAuth 2.0 identifier.
|
||||
@@ -50,12 +52,12 @@ class Auth {
|
||||
final credentialsFilePath = p.join(shorebirdConfigDir, _credentialsFileName);
|
||||
|
||||
http.Client get client {
|
||||
if (credentials == null) return _httpClient;
|
||||
return autoRefreshingClient(_clientId, credentials!, _httpClient);
|
||||
if (_credentials == null) return _httpClient;
|
||||
return autoRefreshingClient(_clientId, _credentials!, _httpClient);
|
||||
}
|
||||
|
||||
Future<void> login(void Function(String) prompt) async {
|
||||
if (credentials != null) return;
|
||||
if (_credentials != null) return;
|
||||
|
||||
final client = http.Client();
|
||||
try {
|
||||
@@ -65,6 +67,7 @@ class Auth {
|
||||
client,
|
||||
prompt,
|
||||
);
|
||||
_user = _credentials?.toUser();
|
||||
_flushCredentials(_credentials!);
|
||||
} finally {
|
||||
client.close();
|
||||
@@ -75,7 +78,11 @@ class Auth {
|
||||
|
||||
AccessCredentials? _credentials;
|
||||
|
||||
AccessCredentials? get credentials => _credentials;
|
||||
User? _user;
|
||||
|
||||
User? get user => _user;
|
||||
|
||||
bool get isAuthenticated => _user != null;
|
||||
|
||||
void _loadCredentials() {
|
||||
final credentialsFile = File(credentialsFilePath);
|
||||
@@ -86,6 +93,7 @@ class Auth {
|
||||
_credentials = AccessCredentials.fromJson(
|
||||
json.decode(contents) as Map<String, dynamic>,
|
||||
);
|
||||
_user = _credentials?.toUser();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +106,7 @@ class Auth {
|
||||
|
||||
void _clearCredentials() {
|
||||
_credentials = null;
|
||||
_user = null;
|
||||
|
||||
final credentialsFile = File(credentialsFilePath);
|
||||
if (credentialsFile.existsSync()) {
|
||||
@@ -109,3 +118,21 @@ class Auth {
|
||||
_httpClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
extension on AccessCredentials {
|
||||
User toUser() {
|
||||
final token = idToken;
|
||||
|
||||
if (token == null) throw Exception('Missing JWT');
|
||||
|
||||
final claims = Jwt.decodeClaims(token);
|
||||
|
||||
if (claims == null) throw Exception('Invalid JWT');
|
||||
|
||||
try {
|
||||
return User(email: claims['email'] as String);
|
||||
} catch (_) {
|
||||
throw Exception('Malformed claims');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'dart:convert';
|
||||
|
||||
/// Jwt Utilities
|
||||
class Jwt {
|
||||
/// Decode and extract claims from a JWT token.
|
||||
static Map<String, dynamic>? decodeClaims(String value) {
|
||||
final parts = value.split('.');
|
||||
if (parts.length != 3) return null;
|
||||
try {
|
||||
return _decodePart(parts[1]);
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _decodePart(String part) {
|
||||
final normalized = base64.normalize(part);
|
||||
final base64Decoded = base64.decode(normalized);
|
||||
final utf8Decoded = utf8.decode(base64Decoded);
|
||||
final jsonDecoded = json.decode(utf8Decoded) as Map<String, dynamic>;
|
||||
return jsonDecoded;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export 'user.dart';
|
||||
@@ -0,0 +1,10 @@
|
||||
/// {@template user}
|
||||
/// A shorebird user account.
|
||||
/// {@endtemplate}
|
||||
class User {
|
||||
/// {@macro user}
|
||||
const User({required this.email});
|
||||
|
||||
/// The user's email address.
|
||||
final String email;
|
||||
}
|
||||
@@ -35,7 +35,7 @@ Defaults to the name in "pubspec.yaml".''',
|
||||
|
||||
@override
|
||||
Future<int>? run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ Defaults to the app_id in "shorebird.yaml".''',
|
||||
|
||||
@override
|
||||
Future<int>? run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class ListAppsCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
|
||||
@override
|
||||
Future<int>? run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class BuildCommand extends ShorebirdCommand
|
||||
|
||||
@override
|
||||
Future<int> run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger
|
||||
..err('You must be logged in to build.')
|
||||
..err("Run 'shorebird login' to log in and try again.");
|
||||
|
||||
@@ -37,7 +37,7 @@ class CreateChannelsCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
|
||||
@override
|
||||
Future<int>? run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in to view channels.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class ListChannelsCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
|
||||
@override
|
||||
Future<int>? run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in to view channels.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class InitCommand extends ShorebirdCommand
|
||||
|
||||
@override
|
||||
Future<int> run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,9 @@ class LoginCommand extends ShorebirdCommand {
|
||||
|
||||
@override
|
||||
Future<int> run() async {
|
||||
final credentials = auth.credentials;
|
||||
if (credentials != null) {
|
||||
if (auth.isAuthenticated) {
|
||||
logger
|
||||
..info('You are already logged in.')
|
||||
..info('You are already logged in as <${auth.user!.email}>.')
|
||||
..info("Run 'shorebird logout' to log out and try again.");
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
@@ -30,7 +29,7 @@ class LoginCommand extends ShorebirdCommand {
|
||||
await auth.login(prompt);
|
||||
logger.info('''
|
||||
|
||||
🎉 ${lightGreen.wrap('Welcome to Shorebird! You are now logged in.')}
|
||||
🎉 ${lightGreen.wrap('Welcome to Shorebird! You are now logged in as <${auth.user!.email}>.')}
|
||||
|
||||
🔑 Credentials are stored in ${lightCyan.wrap(auth.credentialsFilePath)}.
|
||||
🚪 To logout use: "${lightCyan.wrap('shorebird logout')}".''');
|
||||
|
||||
@@ -18,7 +18,7 @@ class LogoutCommand extends ShorebirdCommand {
|
||||
|
||||
@override
|
||||
Future<int> run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.info('You are already logged out.');
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class PatchCommand extends ShorebirdCommand
|
||||
return ExitCode.config.code;
|
||||
}
|
||||
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in to publish.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ make smaller updates to your app.
|
||||
return ExitCode.config.code;
|
||||
}
|
||||
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger.err('You must be logged in to release.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class RunCommand extends ShorebirdCommand
|
||||
|
||||
@override
|
||||
Future<int> run() async {
|
||||
if (auth.credentials == null) {
|
||||
if (!auth.isAuthenticated) {
|
||||
logger
|
||||
..err('You must be logged in to run.')
|
||||
..err("Run 'shorebird login' to log in and try again.");
|
||||
|
||||
@@ -6,19 +6,27 @@ import 'package:test/test.dart';
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
void main() {
|
||||
group('Auth', () {
|
||||
const idToken =
|
||||
'''eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIn0.pD47BhF3MBLyIpfsgWCzP9twzC1HJxGukpcR36DqT6yfiOMHTLcjDbCjRLAnklWEHiT0BQTKTfhs8IousU90Fm5bVKObudfKu8pP5iZZ6Ls4ohDjTrXky9j3eZpZjwv8CnttBVgRfMJG-7YASTFRYFcOLUpnb4Zm5R6QdoCDUYg''';
|
||||
const email = 'test@email.com';
|
||||
final credentials = AccessCredentials(
|
||||
AccessToken('Bearer', 'token', DateTime.now().toUtc()),
|
||||
'refreshToken',
|
||||
AccessToken('Bearer', 'accessToken', DateTime.now().toUtc()),
|
||||
'',
|
||||
[],
|
||||
idToken: idToken,
|
||||
);
|
||||
|
||||
late http.Client httpClient;
|
||||
late AccessCredentials accessCredentials;
|
||||
late Auth auth;
|
||||
|
||||
setUp(() {
|
||||
httpClient = _MockHttpClient();
|
||||
accessCredentials = _MockAccessCredentials();
|
||||
auth = Auth(
|
||||
httpClient: httpClient,
|
||||
obtainAccessCredentials: (clientId, scopes, client, userPrompt) async {
|
||||
@@ -47,42 +55,95 @@ void main() {
|
||||
});
|
||||
|
||||
group('login', () {
|
||||
test('should set the credentials', () async {
|
||||
test('should set the user when claims are valid', () async {
|
||||
when(() => accessCredentials.idToken).thenReturn(idToken);
|
||||
await auth.login((_) {});
|
||||
expect(auth.user, isA<User>().having((u) => u.email, 'email', email));
|
||||
expect(auth.isAuthenticated, isTrue);
|
||||
expect(
|
||||
auth.credentials,
|
||||
isA<AccessCredentials>().having(
|
||||
(c) => c.accessToken.data,
|
||||
'accessToken',
|
||||
credentials.accessToken.data,
|
||||
Auth().user,
|
||||
isA<User>().having((u) => u.email, 'email', email),
|
||||
);
|
||||
expect(Auth().isAuthenticated, isTrue);
|
||||
});
|
||||
|
||||
test('should not set the user when token is null', () async {
|
||||
when(() => accessCredentials.idToken).thenReturn(null);
|
||||
auth = Auth(
|
||||
httpClient: httpClient,
|
||||
obtainAccessCredentials:
|
||||
(clientId, scopes, client, userPrompt) async => accessCredentials,
|
||||
);
|
||||
await expectLater(
|
||||
auth.login((_) {}),
|
||||
throwsA(
|
||||
isA<Exception>().having(
|
||||
(e) => '$e',
|
||||
'description',
|
||||
'Exception: Missing JWT',
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
Auth().credentials,
|
||||
isA<AccessCredentials>().having(
|
||||
(c) => c.accessToken.data,
|
||||
'accessToken',
|
||||
credentials.accessToken.data,
|
||||
expect(auth.user, isNull);
|
||||
expect(auth.isAuthenticated, isFalse);
|
||||
});
|
||||
|
||||
test('should not set the user when token is empty', () async {
|
||||
when(() => accessCredentials.idToken).thenReturn('');
|
||||
auth = Auth(
|
||||
httpClient: httpClient,
|
||||
obtainAccessCredentials:
|
||||
(clientId, scopes, client, userPrompt) async => accessCredentials,
|
||||
);
|
||||
await expectLater(
|
||||
auth.login((_) {}),
|
||||
throwsA(
|
||||
isA<Exception>().having(
|
||||
(e) => '$e',
|
||||
'description',
|
||||
'Exception: Invalid JWT',
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(auth.user, isNull);
|
||||
expect(auth.isAuthenticated, isFalse);
|
||||
});
|
||||
|
||||
test('should not set the user when token claims are malformed', () async {
|
||||
when(() => accessCredentials.idToken).thenReturn(
|
||||
'''eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.LaR0JfOiDrS1AuABC38kzxpSjRLJ_OtfOkZ8hL6I1GPya-cJYwsmqhi5eMBwEbpYHcJhguG5l56XM6dW8xjdK7JbUN6_53gHBosSnL-Ccf29oW71Ado9sxO17YFQyihyMofJ_v78BPVy2H5O10hNjRn_M0JnnAe0Fvd2VrInlIE''',
|
||||
);
|
||||
auth = Auth(
|
||||
httpClient: httpClient,
|
||||
obtainAccessCredentials:
|
||||
(clientId, scopes, client, userPrompt) async => accessCredentials,
|
||||
);
|
||||
await expectLater(
|
||||
auth.login((_) {}),
|
||||
throwsA(
|
||||
isA<Exception>().having(
|
||||
(e) => '$e',
|
||||
'description',
|
||||
'Exception: Malformed claims',
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(auth.user, isNull);
|
||||
expect(auth.isAuthenticated, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('logout', () {
|
||||
test('clears session and wipes state', () async {
|
||||
await auth.login((_) {});
|
||||
expect(
|
||||
auth.credentials,
|
||||
isA<AccessCredentials>().having(
|
||||
(c) => c.accessToken.data,
|
||||
'accessToken',
|
||||
credentials.accessToken.data,
|
||||
),
|
||||
);
|
||||
expect(auth.user, isA<User>().having((u) => u.email, 'email', email));
|
||||
expect(auth.isAuthenticated, isTrue);
|
||||
|
||||
auth.logout();
|
||||
expect(auth.credentials, isNull);
|
||||
expect(Auth().credentials, isNull);
|
||||
expect(auth.user, isNull);
|
||||
expect(auth.isAuthenticated, isFalse);
|
||||
expect(Auth().user, isNull);
|
||||
expect(Auth().isAuthenticated, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:shorebird_cli/src/auth/jwt.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('Jwt', () {
|
||||
group('decodeClaims', () {
|
||||
test('returns null jwt does not contain 3 segments', () {
|
||||
expect(Jwt.decodeClaims('invalid'), isNull);
|
||||
});
|
||||
|
||||
test('returns null when jwt payload segment is malformed', () {
|
||||
expect(Jwt.decodeClaims('this.is.invalid'), isNull);
|
||||
});
|
||||
|
||||
test('returns correct claims when jwt payload segment is valid', () {
|
||||
expect(
|
||||
Jwt.decodeClaims(
|
||||
'''eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIn0.pD47BhF3MBLyIpfsgWCzP9twzC1HJxGukpcR36DqT6yfiOMHTLcjDbCjRLAnklWEHiT0BQTKTfhs8IousU90Fm5bVKObudfKu8pP5iZZ6Ls4ohDjTrXky9j3eZpZjwv8CnttBVgRfMJG-7YASTFRYFcOLUpnb4Zm5R6QdoCDUYg''',
|
||||
),
|
||||
equals({'email': 'test@email.com'}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -17,13 +17,10 @@ class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
void main() {
|
||||
group('create', () {
|
||||
const appId = 'app-id';
|
||||
const displayName = 'Example App';
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late http.Client httpClient;
|
||||
@@ -49,7 +46,7 @@ void main() {
|
||||
logger: logger,
|
||||
)..testArgResults = argResults;
|
||||
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
});
|
||||
|
||||
@@ -58,7 +55,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns no user error when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
@@ -7,8 +7,6 @@ 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 _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
@@ -22,7 +20,6 @@ class _MockLogger extends Mock implements Logger {}
|
||||
void main() {
|
||||
group('delete', () {
|
||||
const appId = 'example';
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late http.Client httpClient;
|
||||
@@ -48,7 +45,7 @@ void main() {
|
||||
logger: logger,
|
||||
)..testArgResults = argResults;
|
||||
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
});
|
||||
|
||||
@@ -60,7 +57,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns no user error when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
@@ -6,8 +6,6 @@ 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 _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
@@ -18,8 +16,6 @@ class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
void main() {
|
||||
group('list', () {
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late http.Client httpClient;
|
||||
late Auth auth;
|
||||
late CodePushClient codePushClient;
|
||||
@@ -43,7 +39,7 @@ void main() {
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
});
|
||||
|
||||
@@ -52,7 +48,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns ExitCode.noUser when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
expect(await command.run(), ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ import 'package:test/test.dart';
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
@@ -30,8 +28,6 @@ class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
void main() {
|
||||
group('build', () {
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late Directory applicationConfigHome;
|
||||
late http.Client httpClient;
|
||||
@@ -70,7 +66,7 @@ void main() {
|
||||
testApplicationConfigHome = (_) => applicationConfigHome.path;
|
||||
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(
|
||||
() => codePushClient.downloadEngine(revision: any(named: 'revision')),
|
||||
@@ -79,7 +75,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('exits with no user when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
|
||||
final result = await buildCommand.run();
|
||||
expect(result, equals(ExitCode.noUser.code));
|
||||
|
||||
@@ -7,8 +7,6 @@ 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 _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
@@ -27,8 +25,6 @@ void main() {
|
||||
const channelName = 'my-channel';
|
||||
const channel = Channel(id: 0, appId: appId, name: channelName);
|
||||
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late http.Client httpClient;
|
||||
late Auth auth;
|
||||
@@ -57,7 +53,7 @@ void main() {
|
||||
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(() => argResults['name']).thenReturn(channelName);
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.confirm(any())).thenReturn(true);
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
@@ -71,7 +67,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns ExitCode.noUser when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
expect(await command.run(), ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ 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 _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
@@ -22,7 +20,6 @@ class _MockLogger extends Mock implements Logger {}
|
||||
void main() {
|
||||
group('list', () {
|
||||
const appId = 'test-app-id';
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late http.Client httpClient;
|
||||
@@ -49,7 +46,7 @@ void main() {
|
||||
)..testArgResults = argResults;
|
||||
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
});
|
||||
|
||||
@@ -61,7 +58,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns ExitCode.noUser when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
expect(await command.run(), ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import 'package:shorebird_cli/src/commands/init_command.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
@@ -34,8 +32,6 @@ version: $version
|
||||
environment:
|
||||
sdk: ">=2.19.0 <3.0.0"''';
|
||||
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late http.Client httpClient;
|
||||
late Auth auth;
|
||||
late CodePushClient codePushClient;
|
||||
@@ -60,7 +56,7 @@ environment:
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(
|
||||
() => codePushClient.createApp(displayName: any(named: 'displayName')),
|
||||
@@ -75,7 +71,7 @@ environment:
|
||||
});
|
||||
|
||||
test('returns no user error when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
@@ -8,15 +8,13 @@ import 'package:shorebird_cli/src/commands/login_command.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
void main() {
|
||||
group('login', () {
|
||||
final credentials = _MockAccessCredentials();
|
||||
const user = User(email: 'test@email.com');
|
||||
|
||||
late Directory applicationConfigHome;
|
||||
late Logger logger;
|
||||
@@ -34,15 +32,19 @@ void main() {
|
||||
when(() => auth.credentialsFilePath).thenReturn(
|
||||
p.join(applicationConfigHome.path, 'credentials.json'),
|
||||
);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
});
|
||||
|
||||
test('exits with code 0 when already logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.user).thenReturn(user);
|
||||
|
||||
final result = await loginCommand.run();
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
|
||||
verify(() => logger.info('You are already logged in.')).called(1);
|
||||
verify(
|
||||
() => logger.info('You are already logged in as <${user.email}>.'),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info("Run 'shorebird logout' to log out and try again."),
|
||||
).called(1);
|
||||
@@ -61,13 +63,16 @@ void main() {
|
||||
|
||||
test('exits with code 0 when logged in successfully', () async {
|
||||
when(() => auth.login(any())).thenAnswer((_) async {});
|
||||
when(() => auth.user).thenReturn(user);
|
||||
|
||||
final result = await loginCommand.run();
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
|
||||
verify(() => auth.login(any())).called(1);
|
||||
verify(
|
||||
() => logger.info(any(that: contains('You are now logged in.'))),
|
||||
() => logger.info(
|
||||
any(that: contains('You are now logged in as <${user.email}>.')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/logout_command.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
@@ -27,6 +25,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('exits with code 0 when already logged out', () async {
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
final result = await logoutCommand.run();
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
|
||||
@@ -36,8 +35,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('exits with code 0 when logged out successfully', () async {
|
||||
final credentials = _MockAccessCredentials();
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
|
||||
final progress = _MockProgress();
|
||||
when(() => progress.complete(any())).thenAnswer((invocation) {});
|
||||
|
||||
@@ -13,8 +13,6 @@ import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _FakeBaseRequest extends Fake implements http.BaseRequest {}
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
@@ -76,8 +74,6 @@ flutter:
|
||||
assets:
|
||||
- shorebird.yaml''';
|
||||
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late Directory applicationConfigHome;
|
||||
late Auth auth;
|
||||
@@ -145,7 +141,7 @@ flutter:
|
||||
when(() => argResults['channel']).thenReturn(channelName);
|
||||
when(() => argResults['dry-run']).thenReturn(false);
|
||||
when(() => argResults['force']).thenReturn(false);
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
when(
|
||||
@@ -218,8 +214,8 @@ flutter:
|
||||
expect(exitCode, ExitCode.config.code);
|
||||
});
|
||||
|
||||
test('throws no user error when session does not exist', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
test('throws no user error when user is not logged in', () async {
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
final tempDir = setUpTempDir();
|
||||
final exitCode = await IOOverrides.runZoned(
|
||||
() => command.run(),
|
||||
|
||||
@@ -13,8 +13,6 @@ import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
@@ -63,8 +61,6 @@ flutter:
|
||||
assets:
|
||||
- shorebird.yaml''';
|
||||
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late Directory applicationConfigHome;
|
||||
late http.Client httpClient;
|
||||
@@ -120,7 +116,7 @@ flutter:
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => argResults['arch']).thenReturn(arch);
|
||||
when(() => argResults['platform']).thenReturn(platform);
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
when(() => logger.confirm(any())).thenReturn(true);
|
||||
@@ -168,8 +164,8 @@ flutter:
|
||||
expect(exitCode, ExitCode.config.code);
|
||||
});
|
||||
|
||||
test('throws no user error when session does not exist', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
test('throws no user error when user is not logged in', () async {
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
final tempDir = setUpTempDir();
|
||||
final exitCode = await IOOverrides.runZoned(
|
||||
() => command.run(),
|
||||
|
||||
@@ -15,8 +15,6 @@ import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAccessCredentials extends Mock implements AccessCredentials {}
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class _MockHttpClient extends Mock implements http.Client {}
|
||||
@@ -33,8 +31,6 @@ class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
void main() {
|
||||
group('run', () {
|
||||
final credentials = _MockAccessCredentials();
|
||||
|
||||
late ArgResults argResults;
|
||||
late Directory applicationConfigHome;
|
||||
late http.Client httpClient;
|
||||
@@ -69,13 +65,13 @@ void main() {
|
||||
testApplicationConfigHome = (_) => applicationConfigHome.path;
|
||||
|
||||
when(() => argResults.rest).thenReturn([]);
|
||||
when(() => auth.credentials).thenReturn(credentials);
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.progress(any())).thenReturn(_MockProgress());
|
||||
});
|
||||
|
||||
test('exits with no user when not logged in', () async {
|
||||
when(() => auth.credentials).thenReturn(null);
|
||||
when(() => auth.isAuthenticated).thenReturn(false);
|
||||
|
||||
final result = await runCommand.run();
|
||||
expect(result, equals(ExitCode.noUser.code));
|
||||
|
||||
Reference in New Issue
Block a user