загрзука клиента и хэширование

This commit is contained in:
2026-03-22 02:00:47 +04:00
parent 73d8798bb9
commit f0463f8cff
9 changed files with 1239 additions and 97 deletions
@@ -0,0 +1,148 @@
import 'dart:io';
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:moonwell_launcher/config.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';
void main() {
group('GameInstallationService', () {
late Directory rootDirectory;
late GameInstallationService service;
setUp(() async {
service = GameInstallationService();
rootDirectory = await Directory.systemTemp.createTemp(
'moonwell_launcher_',
);
await File(
'${rootDirectory.path}${Platform.pathSeparator}Wow.exe',
).writeAsString('wow');
await Directory(
'${rootDirectory.path}${Platform.pathSeparator}Data',
).create(recursive: true);
await File(
'${rootDirectory.path}${Platform.pathSeparator}Data${Platform.pathSeparator}common.MPQ',
).writeAsString('data');
await Directory(
'${rootDirectory.path}${Platform.pathSeparator}Cache',
).create(recursive: true);
await File(
'${rootDirectory.path}${Platform.pathSeparator}Cache${Platform.pathSeparator}cache.bin',
).writeAsString('cache');
await Directory(
'${rootDirectory.path}${Platform.pathSeparator}Logs',
).create(recursive: true);
await File(
'${rootDirectory.path}${Platform.pathSeparator}Logs${Platform.pathSeparator}launcher.log',
).writeAsString('log');
});
tearDown(() async {
if (await rootDirectory.exists()) {
await rootDirectory.delete(recursive: true);
}
});
test('scanInstallation ignores excluded directories', () async {
final snapshot = await service.scanInstallation(rootDirectory.path);
final paths = snapshot.files.map((file) => file.path).toList()..sort();
expect(paths, ['Data/common.MPQ', 'Wow.exe']);
expect(snapshot.buildHash, computeBuildHash(snapshot.files));
});
test(
'scanInstallation creates launcher hash cache outside verification set',
() async {
await service.scanInstallation(rootDirectory.path);
final cacheFile = File(service.getHashCachePath(rootDirectory.path));
expect(await cacheFile.exists(), isTrue);
final secondSnapshot = await service.scanInstallation(
rootDirectory.path,
);
final paths = secondSnapshot.files.map((file) => file.path).toList()
..sort();
expect(paths, ['Data/common.MPQ', 'Wow.exe']);
expect(
await Directory(
'${rootDirectory.path}${Platform.pathSeparator}${Config.launcherMetadataDirectoryName}',
).exists(),
isTrue,
);
},
);
test(
'scanInstallation reuses cached hash when file metadata did not change',
() async {
await service.scanInstallation(rootDirectory.path);
final cacheFile = File(service.getHashCachePath(rootDirectory.path));
final cacheJson =
jsonDecode(await cacheFile.readAsString()) as Map<String, dynamic>;
final entries = (cacheJson['entries'] as Map).cast<String, dynamic>();
entries['Wow.exe'] = {
'size': 3,
'modifiedMs': await File(
'${rootDirectory.path}${Platform.pathSeparator}Wow.exe',
).lastModified().then((value) => value.millisecondsSinceEpoch),
'sha256': 'cached-wow-hash',
};
await cacheFile.writeAsString(
jsonEncode({'version': 1, 'entries': entries}),
flush: true,
);
final snapshot = await service.scanInstallation(rootDirectory.path);
final wowFile = snapshot.files.firstWhere(
(file) => file.path == 'Wow.exe',
);
expect(wowFile.sha256, 'cached-wow-hash');
},
);
test('clearCache recreates an empty cache directory', () async {
await service.clearCache(rootDirectory.path);
final cacheDirectory = Directory(
'${rootDirectory.path}${Platform.pathSeparator}Cache',
);
expect(await cacheDirectory.exists(), isTrue);
expect(await cacheDirectory.list().isEmpty, isTrue);
});
test('ensureParentDirectoryExists creates nested directories', () async {
final tempPath =
'${rootDirectory.path}${Platform.pathSeparator}Data${Platform.pathSeparator}patches${Platform.pathSeparator}common-2.MPQ.moonwell.part';
await service.ensureParentDirectoryExists(tempPath);
expect(
await Directory(
'${rootDirectory.path}${Platform.pathSeparator}Data${Platform.pathSeparator}patches',
).exists(),
isTrue,
);
});
test('resolveClientPath rejects path traversal', () {
expect(
() => service.resolveClientPath(rootDirectory.path, '../evil.txt'),
throwsA(isA<LauncherSyncException>()),
);
});
});
}