wip
This commit is contained in:
@@ -11,6 +11,7 @@ import 'package:moonwell_launcher/features/launcher/data/launcher_api_client.dar
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/client_manifest.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/client_sync_status.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_account.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_game_ticket.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_news_item.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_realm.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_session.dart';
|
||||
@@ -168,6 +169,8 @@ void main() {
|
||||
await pumpEventQueue(times: 20);
|
||||
|
||||
expect(gameService.launchCount, 1);
|
||||
expect(gameService.launchedWith?.account, 'PLAYERONE');
|
||||
expect(gameService.launchedWith?.ticket, 'A1B2C3D4E5F6G7H8');
|
||||
expect(bloc.state.model.isGameRunning, isTrue);
|
||||
expect(bloc.state.model.canPlay, isFalse);
|
||||
|
||||
@@ -234,6 +237,7 @@ class _FakeLauncherApiClient extends LauncherApiClient {
|
||||
final List<LauncherRealm> realms;
|
||||
final LauncherAccount account;
|
||||
int manifestRequestCount = 0;
|
||||
int gameTicketRequestCount = 0;
|
||||
|
||||
@override
|
||||
Future<List<LauncherNewsItem>> fetchNews(String accessToken) async =>
|
||||
@@ -245,6 +249,16 @@ class _FakeLauncherApiClient extends LauncherApiClient {
|
||||
@override
|
||||
Future<LauncherAccount> fetchAccount(String accessToken) async => account;
|
||||
|
||||
@override
|
||||
Future<LauncherGameTicket> issueGameTicket(String accessToken) async {
|
||||
gameTicketRequestCount += 1;
|
||||
return LauncherGameTicket(
|
||||
account: 'PLAYERONE',
|
||||
ticket: 'A1B2C3D4E5F6G7H8',
|
||||
expiresAt: DateTime.now().toUtc().add(const Duration(seconds: 60)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ClientManifest> fetchManifest(String accessToken) async {
|
||||
manifestRequestCount += 1;
|
||||
@@ -272,6 +286,7 @@ class _FakeGameInstallationService extends GameInstallationService {
|
||||
class _TrackingGameInstallationService extends _FakeGameInstallationService {
|
||||
final Completer<int> exitCode = Completer<int>();
|
||||
int launchCount = 0;
|
||||
LauncherGameTicket? launchedWith;
|
||||
|
||||
@override
|
||||
Future<bool> hasClientExecutable(String installationDir) async => true;
|
||||
@@ -280,8 +295,12 @@ class _TrackingGameInstallationService extends _FakeGameInstallationService {
|
||||
Future<void> clearCache(String installationDir) async {}
|
||||
|
||||
@override
|
||||
Future<GameProcessHandle> launchGame(String installationDir) async {
|
||||
Future<GameProcessHandle> launchGame(
|
||||
String installationDir, {
|
||||
required LauncherGameTicket authorization,
|
||||
}) async {
|
||||
launchCount += 1;
|
||||
launchedWith = authorization;
|
||||
return GameProcessHandle(pid: 42, exitCode: exitCode.future);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/game_installation_service.dart';
|
||||
// import 'package:moonwell_launcher/features/launcher/domain/entities/client_hash_entry.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_exception.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_game_ticket.dart';
|
||||
|
||||
void main() {
|
||||
group('GameInstallationService', () {
|
||||
@@ -125,5 +126,45 @@ void main() {
|
||||
throwsA(isA<LauncherSyncException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'launchGame passes authorization only through child environment',
|
||||
() async {
|
||||
final recordingService = _RecordingGameInstallationService();
|
||||
final authorization = LauncherGameTicket(
|
||||
account: 'PLAYERONE',
|
||||
ticket: 'A1B2C3D4E5F6G7H8',
|
||||
expiresAt: DateTime.now().toUtc().add(const Duration(seconds: 60)),
|
||||
);
|
||||
|
||||
await recordingService.launchGame(
|
||||
rootDirectory.path,
|
||||
authorization: authorization,
|
||||
);
|
||||
|
||||
expect(recordingService.arguments, isEmpty);
|
||||
expect(recordingService.environment, <String, String>{
|
||||
'MOONWELL_LAUNCH_ACCOUNT': 'PLAYERONE',
|
||||
'MOONWELL_LAUNCH_TICKET': 'A1B2C3D4E5F6G7H8',
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
final class _RecordingGameInstallationService extends GameInstallationService {
|
||||
Map<String, String>? environment;
|
||||
List<String>? arguments;
|
||||
|
||||
@override
|
||||
Future<GameProcessHandle> startGameProcess({
|
||||
required String executablePath,
|
||||
required List<String> arguments,
|
||||
required String workingDirectory,
|
||||
required Map<String, String> environment,
|
||||
}) async {
|
||||
this.arguments = List<String>.from(arguments);
|
||||
this.environment = Map<String, String>.from(environment);
|
||||
return GameProcessHandle(pid: 42, exitCode: Future<int>.value(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_game_ticket.dart';
|
||||
|
||||
void main() {
|
||||
group('LauncherGameTicket', () {
|
||||
test('parses the launcher authorization contract', () {
|
||||
final ticket = LauncherGameTicket.fromJson(<String, Object?>{
|
||||
'account': 'PLAYERONE',
|
||||
'ticket': 'A1B2C3D4E5F6G7H8',
|
||||
'expires_at': '2026-08-16T12:01:00Z',
|
||||
});
|
||||
|
||||
expect(ticket.account, 'PLAYERONE');
|
||||
expect(ticket.ticket, 'A1B2C3D4E5F6G7H8');
|
||||
expect(ticket.expiresAt, DateTime.utc(2026, 8, 16, 12, 1));
|
||||
});
|
||||
|
||||
test('rejects tickets outside the native client format', () {
|
||||
expect(
|
||||
() => LauncherGameTicket.fromJson(<String, Object?>{
|
||||
'account': 'PLAYERONE',
|
||||
'ticket': 'lowercase-ticket',
|
||||
'expires_at': '2026-08-16T12:01:00Z',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
|
||||
test('does not normalize credentials from the API', () {
|
||||
expect(
|
||||
() => LauncherGameTicket.fromJson(<String, Object?>{
|
||||
'account': 'PLAYER ',
|
||||
'ticket': ' A1B2C3D4E5F6G7H8',
|
||||
'expires_at': '2026-08-16T12:01:00Z',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
|
||||
test('requires enough validity to start the game', () {
|
||||
final ticket = LauncherGameTicket(
|
||||
account: 'PLAYERONE',
|
||||
ticket: 'A1B2C3D4E5F6G7H8',
|
||||
expiresAt: DateTime.utc(2026, 8, 16, 12, 0, 4),
|
||||
);
|
||||
|
||||
expect(ticket.isUsable(now: DateTime.utc(2026, 8, 16, 12)), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects non-string payload fields with a format exception', () {
|
||||
expect(
|
||||
() => LauncherGameTicket.fromJson(<String, dynamic>{
|
||||
'account': 42,
|
||||
'ticket': 'ABCDEFGH12345678',
|
||||
'expires_at': '2026-08-16T20:00:00Z',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user