feat: allow user to choose organization in shorebird init (#2514)
This commit is contained in:
@@ -586,4 +586,4 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.5.0-259.0.dev <4.0.0"
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
|
||||
@@ -80,7 +80,10 @@ class CodePushClientWrapper {
|
||||
|
||||
final CodePushClient codePushClient;
|
||||
|
||||
Future<App> createApp({String? appName}) async {
|
||||
Future<App> createApp({
|
||||
required int organizationId,
|
||||
String? appName,
|
||||
}) async {
|
||||
late final String displayName;
|
||||
if (appName == null) {
|
||||
String? defaultAppName;
|
||||
@@ -96,7 +99,35 @@ class CodePushClientWrapper {
|
||||
displayName = appName;
|
||||
}
|
||||
|
||||
return codePushClient.createApp(displayName: displayName);
|
||||
return codePushClient.createApp(
|
||||
displayName: displayName,
|
||||
organizationId: organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the currently logged in user, or null if no user is logged in.
|
||||
Future<PrivateUser?> getCurrentUser() async {
|
||||
final progress = logger.progress('Fetching user');
|
||||
try {
|
||||
final user = await codePushClient.getCurrentUser();
|
||||
progress.complete();
|
||||
return user;
|
||||
} catch (error) {
|
||||
_handleErrorAndExit(error, progress: progress);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<OrganizationMembership>> getOrganizationMemberships() async {
|
||||
final progress = logger.progress('Fetching organizations');
|
||||
final List<OrganizationMembership> memberships;
|
||||
try {
|
||||
memberships = await codePushClient.getOrganizationMemberships();
|
||||
progress.complete();
|
||||
} catch (error) {
|
||||
_handleErrorAndExit(error, progress: progress);
|
||||
}
|
||||
|
||||
return memberships;
|
||||
}
|
||||
|
||||
Future<List<AppMetadata>> getApps() async {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/doctor.dart';
|
||||
import 'package:shorebird_cli/src/executables/executables.dart';
|
||||
import 'package:shorebird_cli/src/extensions/organization.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_cli/src/platform/ios.dart';
|
||||
import 'package:shorebird_cli/src/pubspec_editor.dart';
|
||||
@@ -65,6 +66,26 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
final orgs = await codePushClientWrapper.getOrganizationMemberships();
|
||||
if (orgs.isEmpty) {
|
||||
logger.err(
|
||||
'''You do not have any organizations. This should never happen. Please contact us on Discord or send us an email at contact@shorebird.dev.''',
|
||||
);
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
final user = await codePushClientWrapper.getCurrentUser();
|
||||
final Organization organization;
|
||||
if (orgs.length > 1) {
|
||||
organization = logger.chooseOne(
|
||||
'Which organization should this app belong to?',
|
||||
choices: orgs.map((o) => o.organization).toList(),
|
||||
display: (o) => o.displayName(user: user!),
|
||||
);
|
||||
} else {
|
||||
organization = orgs.first.organization;
|
||||
}
|
||||
|
||||
final force = results['force'] == true;
|
||||
|
||||
Set<String>? androidFlavors;
|
||||
@@ -130,6 +151,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
for (final flavor in newFlavors) {
|
||||
final app = await codePushClientWrapper.createApp(
|
||||
appName: '$deflavoredAppName ($flavor)',
|
||||
organizationId: organization.id,
|
||||
);
|
||||
flavorsToAppIds[flavor] = app.id;
|
||||
}
|
||||
@@ -171,17 +193,24 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
if (hasNoFlavors) {
|
||||
// No platforms have any flavors so we just create a single app
|
||||
// and assign it as the default.
|
||||
final app = await codePushClientWrapper.createApp(appName: displayName);
|
||||
final app = await codePushClientWrapper.createApp(
|
||||
appName: displayName,
|
||||
organizationId: organization.id,
|
||||
);
|
||||
appId = app.id;
|
||||
} else if (hasSomeFlavors) {
|
||||
// Some platforms have flavors and some do not so we create an app
|
||||
// for the default (no flavor) and then create an app per flavor.
|
||||
final app = await codePushClientWrapper.createApp(appName: displayName);
|
||||
final app = await codePushClientWrapper.createApp(
|
||||
appName: displayName,
|
||||
organizationId: organization.id,
|
||||
);
|
||||
appId = app.id;
|
||||
final values = <String, String>{};
|
||||
for (final flavor in productFlavors) {
|
||||
final app = await codePushClientWrapper.createApp(
|
||||
appName: '$displayName ($flavor)',
|
||||
organizationId: organization.id,
|
||||
);
|
||||
values[flavor] = app.id;
|
||||
}
|
||||
@@ -193,6 +222,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
for (final flavor in productFlavors) {
|
||||
final app = await codePushClientWrapper.createApp(
|
||||
appName: '$displayName ($flavor)',
|
||||
organizationId: organization.id,
|
||||
);
|
||||
values[flavor] = app.id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
|
||||
/// {@template organization_display}
|
||||
/// Returns the user-facing display name of an [Organization].
|
||||
/// {@endtemplate}
|
||||
extension OrganizationDisplay on Organization {
|
||||
/// {@macro organization_display}
|
||||
String displayName({required PrivateUser user}) => switch (organizationType) {
|
||||
OrganizationType.team => name,
|
||||
OrganizationType.personal => user.displayName ?? user.email,
|
||||
};
|
||||
}
|
||||
@@ -212,44 +212,141 @@ void main() {
|
||||
).thenAnswer((_) async => flutterVersion);
|
||||
});
|
||||
|
||||
group('getCurrentUser', () {
|
||||
group('when getCurrentUser request fails', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => codePushClient.getCurrentUser(),
|
||||
).thenThrow(Exception('something went wrong'));
|
||||
});
|
||||
|
||||
test('exits with code 70', () async {
|
||||
await expectLater(
|
||||
() async => runWithOverrides(codePushClientWrapper.getCurrentUser),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
verify(() => progress.fail(any())).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when getCurrentUser request succeeds', () {
|
||||
final user = PrivateUser.forTest();
|
||||
setUp(() {
|
||||
when(() => codePushClient.getCurrentUser()).thenAnswer(
|
||||
(_) async => user,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns current user', () async {
|
||||
final result = await runWithOverrides(
|
||||
codePushClientWrapper.getCurrentUser,
|
||||
);
|
||||
expect(result, user);
|
||||
verify(() => progress.complete()).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('app', () {
|
||||
const organizationId = 123;
|
||||
|
||||
group('createApp', () {
|
||||
test('prompts for displayName when not provided', () async {
|
||||
const appName = 'test app';
|
||||
const app = App(id: appId, displayName: 'Test App');
|
||||
when(() => logger.prompt(any())).thenReturn(appName);
|
||||
when(() => codePushClient.createApp(displayName: appName)).thenAnswer(
|
||||
when(
|
||||
() => codePushClient.createApp(
|
||||
displayName: appName,
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => app,
|
||||
);
|
||||
|
||||
await runWithOverrides(
|
||||
() => codePushClientWrapper.createApp(),
|
||||
() => codePushClientWrapper.createApp(
|
||||
organizationId: organizationId,
|
||||
),
|
||||
);
|
||||
|
||||
verify(() => logger.prompt(any())).called(1);
|
||||
verify(
|
||||
() => codePushClient.createApp(displayName: appName),
|
||||
() => codePushClient.createApp(
|
||||
displayName: appName,
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('does not prompt for displayName when not provided', () async {
|
||||
const appName = 'test app';
|
||||
const app = App(id: appId, displayName: 'Test App');
|
||||
when(() => codePushClient.createApp(displayName: appName)).thenAnswer(
|
||||
when(
|
||||
() => codePushClient.createApp(
|
||||
displayName: appName,
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => app,
|
||||
);
|
||||
|
||||
await runWithOverrides(
|
||||
() => codePushClientWrapper.createApp(appName: appName),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: appName,
|
||||
organizationId: organizationId,
|
||||
),
|
||||
);
|
||||
|
||||
verifyNever(() => logger.prompt(any()));
|
||||
verify(
|
||||
() => codePushClient.createApp(displayName: appName),
|
||||
() => codePushClient.createApp(
|
||||
displayName: appName,
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getOrganizationMemberships', () {
|
||||
test('exits with code 70 when getting organization memberships fails',
|
||||
() async {
|
||||
const error = 'something went wrong';
|
||||
when(() => codePushClient.getOrganizationMemberships())
|
||||
.thenThrow(error);
|
||||
|
||||
await expectLater(
|
||||
() async => runWithOverrides(
|
||||
codePushClientWrapper.getOrganizationMemberships,
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
verify(() => progress.fail(error)).called(1);
|
||||
});
|
||||
|
||||
test('returns organization memberships on success', () async {
|
||||
final expectedMemberships = [
|
||||
OrganizationMembership(
|
||||
organization: Organization.forTest(),
|
||||
role: OrganizationRole.admin,
|
||||
),
|
||||
OrganizationMembership(
|
||||
organization: Organization.forTest(),
|
||||
role: OrganizationRole.member,
|
||||
),
|
||||
];
|
||||
when(() => codePushClient.getOrganizationMemberships())
|
||||
.thenAnswer((_) async => expectedMemberships);
|
||||
|
||||
final memberships = await runWithOverrides(
|
||||
codePushClientWrapper.getOrganizationMemberships,
|
||||
);
|
||||
|
||||
expect(memberships, equals(expectedMemberships));
|
||||
verify(() => progress.complete()).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getApps', () {
|
||||
test('exits with code 70 when getting apps fails', () async {
|
||||
const error = 'something went wrong';
|
||||
|
||||
@@ -35,6 +35,8 @@ name: $appName
|
||||
version: $version
|
||||
environment:
|
||||
sdk: ">=2.19.0 <3.0.0"''';
|
||||
const organizationId = 123;
|
||||
final currentUser = PrivateUser.forTest();
|
||||
|
||||
late ArgResults argResults;
|
||||
late Doctor doctor;
|
||||
@@ -92,8 +94,22 @@ environment:
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
shorebirdValidator = MockShorebirdValidator();
|
||||
|
||||
when(() => codePushClientWrapper.getCurrentUser()).thenAnswer(
|
||||
(_) async => currentUser,
|
||||
);
|
||||
when(() => codePushClientWrapper.getOrganizationMemberships()).thenAnswer(
|
||||
(_) async => [
|
||||
OrganizationMembership(
|
||||
role: OrganizationRole.owner,
|
||||
organization: Organization.forTest(id: organizationId),
|
||||
),
|
||||
],
|
||||
);
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((_) async => app);
|
||||
when(
|
||||
() => doctor.runValidators(any(), applyFixes: any(named: 'applyFixes')),
|
||||
@@ -200,7 +216,10 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
|
||||
);
|
||||
verify(
|
||||
() => codePushClientWrapper.createApp(appName: appName),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: appName,
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
@@ -211,7 +230,10 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
|
||||
);
|
||||
verify(
|
||||
() => codePushClientWrapper.createApp(appName: appName),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: appName,
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
@@ -249,7 +271,10 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
test('throws software error when error occurs creating app.', () async {
|
||||
final error = Exception('oops');
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenThrow(error);
|
||||
final exitCode = await runWithOverrides(command.run);
|
||||
verify(
|
||||
@@ -259,6 +284,112 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
expect(exitCode, ExitCode.software.code);
|
||||
});
|
||||
|
||||
group('when user has no organizations', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => codePushClientWrapper.getOrganizationMemberships(),
|
||||
).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('exits with software error code', () async {
|
||||
final exitCode = await runWithOverrides(command.run);
|
||||
expect(exitCode, equals(ExitCode.software.code));
|
||||
verify(
|
||||
() => logger.err(
|
||||
'''You do not have any organizations. This should never happen. Please contact us on Discord or send us an email at contact@shorebird.dev.''',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when user has only one organization', () {
|
||||
setUp(() {
|
||||
when(() => codePushClientWrapper.getOrganizationMemberships())
|
||||
.thenAnswer(
|
||||
(_) async => [
|
||||
OrganizationMembership(
|
||||
role: OrganizationRole.owner,
|
||||
organization: Organization.forTest(id: organizationId),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('does not prompt for organization, uses that org id to create app',
|
||||
() async {
|
||||
final exitCode = await runWithOverrides(command.run);
|
||||
expect(exitCode, equals(ExitCode.success.code));
|
||||
verifyNever(
|
||||
() => logger.chooseOne(
|
||||
'Which organization should this app belong to?',
|
||||
choices: any(named: 'choices'),
|
||||
),
|
||||
);
|
||||
verify(
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: appName,
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when user has multiple organizations', () {
|
||||
final org1 = Organization.forTest(
|
||||
name: 'org1',
|
||||
id: 1,
|
||||
);
|
||||
final org2 = Organization.forTest(
|
||||
name: 'org2',
|
||||
id: 2,
|
||||
organizationType: OrganizationType.team,
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
when(() => codePushClientWrapper.getOrganizationMemberships())
|
||||
.thenAnswer(
|
||||
(_) async => [
|
||||
OrganizationMembership(
|
||||
role: OrganizationRole.owner,
|
||||
organization: org1,
|
||||
),
|
||||
OrganizationMembership(
|
||||
role: OrganizationRole.owner,
|
||||
organization: org2,
|
||||
),
|
||||
],
|
||||
);
|
||||
when(
|
||||
() => logger.chooseOne<Organization>(
|
||||
'Which organization should this app belong to?',
|
||||
choices: any(named: 'choices'),
|
||||
display: any(named: 'display'),
|
||||
),
|
||||
).thenReturn(org2);
|
||||
});
|
||||
|
||||
test('prompts for organization and uses that org id to create app',
|
||||
() async {
|
||||
final exitCode = await runWithOverrides(command.run);
|
||||
expect(exitCode, equals(ExitCode.success.code));
|
||||
final capturedDisplay = verify(
|
||||
() => logger.chooseOne<Organization>(
|
||||
'Which organization should this app belong to?',
|
||||
choices: [org1, org2],
|
||||
display: captureAny(named: 'display'),
|
||||
),
|
||||
).captured.single as String Function(Organization);
|
||||
expect(capturedDisplay(org1), equals(currentUser.email));
|
||||
expect(capturedDisplay(org2), equals('org2'));
|
||||
verify(
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: appName,
|
||||
organizationId: org2.id,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('on non MacOS', () {
|
||||
setUp(() {
|
||||
when(() => platform.isMacOS).thenReturn(false);
|
||||
@@ -312,7 +443,10 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
).called(1);
|
||||
verifyNever(() => logger.prompt(any()));
|
||||
verify(
|
||||
() => codePushClientWrapper.createApp(appName: displayName),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: displayName,
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
@@ -346,7 +480,10 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
const appIds = ['test-appId-1', 'test-appId-2'];
|
||||
var index = 0;
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: appIds[index++], displayName: appName ?? '-');
|
||||
@@ -373,8 +510,14 @@ flavors:
|
||||
),
|
||||
).called(1);
|
||||
verifyInOrder([
|
||||
() => codePushClientWrapper.createApp(appName: '$appName (internal)'),
|
||||
() => codePushClientWrapper.createApp(appName: '$appName (stable)'),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (internal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (stable)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -410,7 +553,10 @@ flavors:
|
||||
},
|
||||
);
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: appIds[index++], displayName: appName ?? '--');
|
||||
@@ -434,21 +580,27 @@ flavors:
|
||||
verifyInOrder([
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (development)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (developmentInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (production)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (productionInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (staging)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (stagingInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
]);
|
||||
});
|
||||
@@ -474,7 +626,10 @@ flavors:
|
||||
},
|
||||
);
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: appIds[index++], displayName: appName ?? '-');
|
||||
@@ -501,21 +656,27 @@ flavors:
|
||||
verifyInOrder([
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (development)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (developmentInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (production)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (productionInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (staging)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (stagingInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
]);
|
||||
});
|
||||
@@ -543,7 +704,10 @@ flavors:
|
||||
);
|
||||
when(() => gradlew.productFlavors(any())).thenAnswer((_) async => {});
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: appIds[index++], displayName: appName ?? '--');
|
||||
@@ -567,21 +731,27 @@ flavors:
|
||||
verifyInOrder([
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (development)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (developmentInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (production)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (productionInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (staging)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (stagingInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
]);
|
||||
});
|
||||
@@ -609,7 +779,10 @@ flavors:
|
||||
},
|
||||
);
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: appIds[index++], displayName: appName ?? '-');
|
||||
@@ -633,21 +806,27 @@ flavors:
|
||||
verifyInOrder([
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (development)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (developmentInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (production)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (productionInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (staging)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (stagingInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
]);
|
||||
});
|
||||
@@ -675,7 +854,10 @@ flavors:
|
||||
).thenAnswer((_) async => variants);
|
||||
when(() => ios.flavors()).thenReturn(variants);
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: appIds[index++], displayName: appName ?? '-');
|
||||
@@ -699,21 +881,27 @@ flavors:
|
||||
verifyInOrder([
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (development)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (developmentInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (production)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (productionInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (staging)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (stagingInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
]);
|
||||
});
|
||||
@@ -749,7 +937,10 @@ flavors:
|
||||
).thenAnswer((_) async => androidVariants);
|
||||
when(() => ios.flavors()).thenReturn(iosVariants);
|
||||
when(
|
||||
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: appIds[index++], displayName: appName ?? '-');
|
||||
@@ -773,27 +964,37 @@ flavors:
|
||||
),
|
||||
);
|
||||
verifyInOrder([
|
||||
() => codePushClientWrapper.createApp(appName: '$appName (dev)'),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (dev)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (devInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (production)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (productionInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (development)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (developmentInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (staging)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (stagingInternal)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
]);
|
||||
});
|
||||
@@ -842,8 +1043,10 @@ flavors:
|
||||
),
|
||||
);
|
||||
when(
|
||||
() =>
|
||||
codePushClientWrapper.createApp(appName: any(named: 'appName')),
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: any(named: 'appName'),
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final appName = invocation.namedArguments[#appName] as String?;
|
||||
return App(id: newAppIds[index++], displayName: appName ?? '-');
|
||||
@@ -855,21 +1058,25 @@ flavors:
|
||||
verifyNever(
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (a)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
);
|
||||
verifyNever(
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (b)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
);
|
||||
verify(
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (c)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => codePushClientWrapper.createApp(
|
||||
appName: '$appName (d)',
|
||||
organizationId: organizationId,
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:shorebird_cli/src/extensions/organization.dart';
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('OrganizationDisplay', () {
|
||||
group('displayName', () {
|
||||
test('should return the name for a team organization', () {
|
||||
final organization = Organization.forTest(
|
||||
organizationType: OrganizationType.team,
|
||||
name: 'My Organization',
|
||||
);
|
||||
expect(
|
||||
organization.displayName(user: PrivateUser.forTest()),
|
||||
equals('My Organization'),
|
||||
);
|
||||
});
|
||||
|
||||
group('for a personal organization', () {
|
||||
group('when the user has a display name', () {
|
||||
final user = PrivateUser.forTest(displayName: 'John Doe');
|
||||
|
||||
test("should return the user's display name", () {
|
||||
final organization = Organization.forTest();
|
||||
expect(organization.displayName(user: user), 'John Doe');
|
||||
});
|
||||
});
|
||||
|
||||
group('when the user does not have a display name', () {
|
||||
final user = PrivateUser.forTest(email: 'test@test.com');
|
||||
|
||||
test("should return the user's email", () {
|
||||
final organization = Organization.forTest();
|
||||
expect(
|
||||
organization.displayName(user: user),
|
||||
equals('test@test.com'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -8,9 +8,13 @@ Future<void> main() async {
|
||||
// List all apps.
|
||||
final apps = await client.getApps();
|
||||
|
||||
// Get the list of organization memberships for the current user.
|
||||
final memberships = await client.getOrganizationMemberships();
|
||||
|
||||
// Create a new Shorebird application.
|
||||
final app = await client.createApp(
|
||||
displayName: '<DISPLAY NAME>', // e.g. 'Shorebird Example'
|
||||
organizationId: memberships.first.organization.id,
|
||||
);
|
||||
|
||||
// Create a channel.
|
||||
|
||||
@@ -225,10 +225,18 @@ class CodePushClient {
|
||||
|
||||
/// Create a new app with the provided [displayName].
|
||||
/// Returns the newly created app.
|
||||
Future<App> createApp({required String displayName}) async {
|
||||
Future<App> createApp({
|
||||
required int organizationId,
|
||||
required String displayName,
|
||||
}) async {
|
||||
final response = await _httpClient.post(
|
||||
Uri.parse('$_v1/apps'),
|
||||
body: json.encode({'display_name': displayName}),
|
||||
body: json.encode(
|
||||
CreateAppRequest(
|
||||
organizationId: organizationId,
|
||||
displayName: displayName,
|
||||
).toJson(),
|
||||
),
|
||||
);
|
||||
|
||||
if (!response.isSuccess) {
|
||||
|
||||
@@ -19,6 +19,7 @@ void main() {
|
||||
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
|
||||
const flutterVersion = '3.22.0';
|
||||
const displayName = 'shorebird-example';
|
||||
const organizationId = 1234;
|
||||
const errorResponse = ErrorResponse(
|
||||
code: 'test_code',
|
||||
message: 'test message',
|
||||
@@ -725,7 +726,12 @@ void main() {
|
||||
|
||||
group('createApp', () {
|
||||
test('makes the correct request', () async {
|
||||
codePushClient.createApp(displayName: displayName).ignore();
|
||||
codePushClient
|
||||
.createApp(
|
||||
organizationId: organizationId,
|
||||
displayName: displayName,
|
||||
)
|
||||
.ignore();
|
||||
final request = verify(() => httpClient.send(captureAny()))
|
||||
.captured
|
||||
.single as http.BaseRequest;
|
||||
@@ -743,7 +749,10 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
codePushClient.createApp(displayName: displayName),
|
||||
codePushClient.createApp(
|
||||
organizationId: organizationId,
|
||||
displayName: displayName,
|
||||
),
|
||||
throwsA(
|
||||
isA<CodePushException>().having(
|
||||
(e) => e.message,
|
||||
@@ -763,7 +772,10 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
codePushClient.createApp(displayName: displayName),
|
||||
codePushClient.createApp(
|
||||
organizationId: organizationId,
|
||||
displayName: displayName,
|
||||
),
|
||||
throwsA(
|
||||
isA<CodePushException>().having(
|
||||
(e) => e.message,
|
||||
@@ -787,7 +799,10 @@ void main() {
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
codePushClient.createApp(displayName: displayName),
|
||||
codePushClient.createApp(
|
||||
organizationId: organizationId,
|
||||
displayName: displayName,
|
||||
),
|
||||
completion(
|
||||
equals(
|
||||
isA<App>()
|
||||
|
||||
Reference in New Issue
Block a user