обновил лаунчер под новую версию веба
This commit is contained in:
@@ -173,13 +173,8 @@ void main() {
|
||||
await pumpEventQueue(times: 20);
|
||||
|
||||
expect(gameService.launchCount, 1);
|
||||
expect(gameService.savedAccountNameRemovalCount, 1);
|
||||
expect(launchOperations, <String>[
|
||||
'removeSavedAccountName',
|
||||
'clearCache',
|
||||
'issueGameTicket',
|
||||
'launchGame',
|
||||
]);
|
||||
expect(gameService.savedAccountNameRemovalCount, 0);
|
||||
expect(launchOperations, <String>['issueGameTicket', 'launchGame']);
|
||||
expect(gameService.launchedWith?.account, 'PLAYERONE');
|
||||
expect(gameService.launchedWith?.ticket, 'A1B2C3D4E5F6G7H8');
|
||||
expect(bloc.state.model.isGameRunning, isTrue);
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:moonwell_launcher/features/launcher/domain/entities/client_manif
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/client_manifest_file.dart';
|
||||
// import 'package:moonwell_launcher/features/launcher/domain/entities/client_sync_status.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_exception.dart';
|
||||
|
||||
// import 'package:moonwell_launcher/features/launcher/domain/entities/local_client_file.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -13,14 +13,17 @@ void main() {
|
||||
late GameInstallationService service;
|
||||
|
||||
setUp(() async {
|
||||
service = GameInstallationService();
|
||||
service = _RecordingGameInstallationService();
|
||||
rootDirectory = await Directory.systemTemp.createTemp(
|
||||
'moonwell_launcher_',
|
||||
);
|
||||
|
||||
await File(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}Wow.exe',
|
||||
).writeAsString('wow');
|
||||
service.getExecutablePath(rootDirectory.path),
|
||||
).writeAsString('moonwell');
|
||||
await File(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}realmlist.wtf',
|
||||
).writeAsString('set realmlist 127.0.0.1:3724\n');
|
||||
await Directory(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}Data',
|
||||
).create(recursive: true);
|
||||
@@ -167,18 +170,196 @@ void main() {
|
||||
);
|
||||
|
||||
expect(recordingService.arguments, isEmpty);
|
||||
expect(
|
||||
recordingService.executablePath,
|
||||
service.getExecutablePath(rootDirectory.path),
|
||||
);
|
||||
expect(recordingService.workingDirectory, rootDirectory.path);
|
||||
expect(recordingService.environment, <String, String>{
|
||||
'MOONWELL_LAUNCH_ACCOUNT': 'PLAYERONE',
|
||||
'MOONWELL_LAUNCH_TICKET': 'A1B2C3D4E5F6G7H8',
|
||||
'WOW_USER': 'PLAYERONE',
|
||||
'WOW_PASS': 'A1B2C3D4E5F6G7H8',
|
||||
'WOW_HOST': '127.0.0.1:3724',
|
||||
'WOW_DATA': '${rootDirectory.path}${Platform.pathSeparator}Data',
|
||||
'BENILLA_HOME':
|
||||
'${rootDirectory.path}${Platform.pathSeparator}benilla-config',
|
||||
});
|
||||
expect(
|
||||
await File(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}realmlist.wtf',
|
||||
).readAsString(),
|
||||
'set realmlist 127.0.0.1:3724\n',
|
||||
);
|
||||
expect(
|
||||
await File(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}Cache${Platform.pathSeparator}cache.bin',
|
||||
).readAsString(),
|
||||
'cache',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'macOS starts the app launcher and keeps state outside the bundle',
|
||||
() async {
|
||||
final recordingService = _RecordingGameInstallationService(
|
||||
platform: 'macos',
|
||||
);
|
||||
final executable = File(
|
||||
recordingService.getExecutablePath(rootDirectory.path),
|
||||
);
|
||||
await executable.parent.create(recursive: true);
|
||||
await executable.writeAsString('#!/bin/sh\n');
|
||||
await recordingService.launchGame(
|
||||
rootDirectory.path,
|
||||
authorization: _validTicket(),
|
||||
);
|
||||
expect(recordingService.executablePath, executable.path);
|
||||
expect(
|
||||
recordingService.environment?['BENILLA_HOME'],
|
||||
'${rootDirectory.path}${Platform.pathSeparator}benilla-config',
|
||||
);
|
||||
expect(recordingService.arguments, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
test('uses WoW/Data when the install has a nested Data directory', () async {
|
||||
await Directory(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}Data',
|
||||
).rename('${rootDirectory.path}${Platform.pathSeparator}WoW');
|
||||
final nestedData = Directory(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}WoW${Platform.pathSeparator}Data',
|
||||
);
|
||||
await nestedData.create();
|
||||
final recordingService = _RecordingGameInstallationService();
|
||||
await recordingService.launchGame(
|
||||
rootDirectory.path,
|
||||
authorization: _validTicket(),
|
||||
);
|
||||
expect(recordingService.environment?['WOW_DATA'], nestedData.path);
|
||||
});
|
||||
|
||||
test('configured auth address overrides the install realmlist', () async {
|
||||
final configured = _RecordingGameInstallationService(
|
||||
authAddress: 'login.example:3725',
|
||||
);
|
||||
expect(
|
||||
await configured.resolveAuthAddress(rootDirectory.path),
|
||||
'login.example:3725',
|
||||
);
|
||||
});
|
||||
|
||||
test('reads a quoted realmlist with CRLF without modifying it', () async {
|
||||
final file = File(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}realmlist.wtf',
|
||||
);
|
||||
await file.writeAsString('SET REALMLIST "login.example:3724"\r\n');
|
||||
expect(
|
||||
await service.resolveAuthAddress(rootDirectory.path),
|
||||
'login.example:3724',
|
||||
);
|
||||
expect(
|
||||
await file.readAsString(),
|
||||
'SET REALMLIST "login.example:3724"\r\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects missing or malformed auth endpoints', () async {
|
||||
final file = File(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}realmlist.wtf',
|
||||
);
|
||||
await file.delete();
|
||||
await expectLater(
|
||||
service.resolveAuthAddress(rootDirectory.path),
|
||||
throwsA(isA<LauncherSyncException>()),
|
||||
);
|
||||
for (final address in [
|
||||
'https://login.example',
|
||||
'login.example:0',
|
||||
'login.example:65536',
|
||||
]) {
|
||||
final configured = _RecordingGameInstallationService(
|
||||
authAddress: address,
|
||||
);
|
||||
await expectLater(
|
||||
configured.resolveAuthAddress(rootDirectory.path),
|
||||
throwsA(isA<LauncherSyncException>()),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('does not start a process with an expired ticket', () async {
|
||||
final recordingService = _RecordingGameInstallationService();
|
||||
await expectLater(
|
||||
recordingService.launchGame(
|
||||
rootDirectory.path,
|
||||
authorization: LauncherGameTicket(
|
||||
account: 'PLAYERONE',
|
||||
ticket: 'A1B2C3D4E5F6G7H8',
|
||||
expiresAt: DateTime.utc(2020),
|
||||
),
|
||||
),
|
||||
throwsA(isA<LauncherSyncException>()),
|
||||
);
|
||||
expect(recordingService.environment, isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'does not launch a missing client or an installation without data',
|
||||
() async {
|
||||
final recordingService = _RecordingGameInstallationService();
|
||||
final executable = File(
|
||||
recordingService.getExecutablePath(rootDirectory.path),
|
||||
);
|
||||
await executable.delete();
|
||||
await expectLater(
|
||||
recordingService.launchGame(
|
||||
rootDirectory.path,
|
||||
authorization: _validTicket(),
|
||||
),
|
||||
throwsA(isA<LauncherSyncException>()),
|
||||
);
|
||||
await executable.writeAsString('moonwell');
|
||||
await Directory(
|
||||
'${rootDirectory.path}${Platform.pathSeparator}Data',
|
||||
).delete(recursive: true);
|
||||
await expectLater(
|
||||
recordingService.launchGame(
|
||||
rootDirectory.path,
|
||||
authorization: _validTicket(),
|
||||
),
|
||||
throwsA(isA<LauncherSyncException>()),
|
||||
);
|
||||
expect(recordingService.environment, isNull);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
LauncherGameTicket _validTicket() => LauncherGameTicket(
|
||||
account: 'PLAYERONE',
|
||||
ticket: 'A1B2C3D4E5F6G7H8',
|
||||
expiresAt: DateTime.now().toUtc().add(const Duration(seconds: 60)),
|
||||
);
|
||||
|
||||
final class _RecordingGameInstallationService extends GameInstallationService {
|
||||
_RecordingGameInstallationService({
|
||||
this.platform = 'windows',
|
||||
this.authAddress = '',
|
||||
});
|
||||
|
||||
final String platform;
|
||||
final String authAddress;
|
||||
|
||||
@override
|
||||
String get operatingSystem => platform;
|
||||
|
||||
@override
|
||||
String get configuredAuthAddress => authAddress;
|
||||
|
||||
Map<String, String>? environment;
|
||||
List<String>? arguments;
|
||||
String? executablePath;
|
||||
String? workingDirectory;
|
||||
|
||||
@override
|
||||
Future<GameProcessHandle> startGameProcess({
|
||||
@@ -189,6 +370,8 @@ final class _RecordingGameInstallationService extends GameInstallationService {
|
||||
}) async {
|
||||
this.arguments = List<String>.from(arguments);
|
||||
this.environment = Map<String, String>.from(environment);
|
||||
this.executablePath = executablePath;
|
||||
this.workingDirectory = workingDirectory;
|
||||
return GameProcessHandle(pid: 42, exitCode: Future<int>.value(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/launcher_api_client.dart';
|
||||
|
||||
void main() {
|
||||
test('requests a Vanilla game ticket with bearer authentication', () async {
|
||||
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
addTearDown(() => server.close(force: true));
|
||||
final client = _LocalLauncherApiClient(server.port);
|
||||
final ticketFuture = client.issueGameTicket('test-launcher-token');
|
||||
final request = await server.first;
|
||||
expect(request.method, 'POST');
|
||||
expect(request.uri.path, '/api/launcher/game-ticket');
|
||||
expect(
|
||||
request.headers.value(HttpHeaders.authorizationHeader),
|
||||
'Bearer test-launcher-token',
|
||||
);
|
||||
expect(jsonDecode(await utf8.decoder.bind(request).join()), {
|
||||
'client_build': 5875,
|
||||
});
|
||||
request.response.headers.contentType = ContentType.json;
|
||||
request.response.write(
|
||||
jsonEncode({
|
||||
'account': 'PLAYERONE',
|
||||
'ticket': 'A1B2C3D4E5F6G7H8',
|
||||
'expires_at': DateTime.now()
|
||||
.toUtc()
|
||||
.add(const Duration(seconds: 60))
|
||||
.toIso8601String(),
|
||||
}),
|
||||
);
|
||||
await request.response.close();
|
||||
final ticket = await ticketFuture;
|
||||
expect(ticket.account, 'PLAYERONE');
|
||||
expect(ticket.ticket, 'A1B2C3D4E5F6G7H8');
|
||||
});
|
||||
}
|
||||
|
||||
class _LocalLauncherApiClient extends LauncherApiClient {
|
||||
_LocalLauncherApiClient(this.port);
|
||||
|
||||
final int port;
|
||||
|
||||
@override
|
||||
String get apiBaseUrl => 'http://127.0.0.1:$port';
|
||||
}
|
||||
@@ -37,6 +37,24 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects accounts incompatible with the web 1.12.1 contract', () {
|
||||
for (final account in [
|
||||
'AB',
|
||||
'ABCDEFGHIJKLMNOPQ',
|
||||
'PLAYER_NAME',
|
||||
'playerone',
|
||||
]) {
|
||||
expect(
|
||||
() => LauncherGameTicket.fromJson(<String, Object?>{
|
||||
'account': account,
|
||||
'ticket': 'A1B2C3D4E5F6G7H8',
|
||||
'expires_at': '2026-08-16T12:01:00Z',
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('requires enough validity to start the game', () {
|
||||
final ticket = LauncherGameTicket(
|
||||
account: 'PLAYERONE',
|
||||
|
||||
Reference in New Issue
Block a user