обновил лаунчер под новую версию веба
This commit is contained in:
@@ -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';
|
||||
}
|
||||
Reference in New Issue
Block a user