Files
FlClash/test/core/spill_test.dart
T
chen08209 5984c161c5 fix(core): keep very large profiles from crashing the app
Changelog: Large profiles no longer crash; downloads trust core CAs
2026-10-16 22:54:53 +08:00

122 lines
3.6 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:fl_clash/core/method.dart';
import 'package:fl_clash/core/spill.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart';
import '../helpers/app_paths.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late Directory home;
late Directory spillDirectory;
setUp(() async {
home = Directory.systemTemp.createTempSync('spill_test');
await useAppPaths(home);
spillDirectory = Directory(join(home.path, coreSpillDirectoryName))
..createSync();
});
tearDown(() => home.deleteSync(recursive: true));
File spill(Directory directory, Map<String, Object?> envelope) {
return File(join(directory.path, '1.json'))
..writeAsStringSync(json.encode(envelope));
}
test('an answer sent inline passes through', () async {
const response = CoreMethodResponse(id: '1', result: 'inline');
expect(await resolveSpilledResponse(response), same(response));
});
test('reads a spilled answer and deletes its file', () async {
final file = spill(spillDirectory, {
'id': '2',
'result': {'mode': 'rule'},
});
final response = await resolveSpilledResponse(
CoreMethodResponse(id: '2', file: file.path),
);
expect(response.id, '2');
expect(response.unwrap<Map<String, dynamic>>(), {'mode': 'rule'});
expect(file.existsSync(), isFalse);
});
test('refuses a file outside the spill directory', () async {
final file = spill(home, {'id': '3', 'result': 'secret'});
final response = await resolveSpilledResponse(
CoreMethodResponse(id: '3', file: file.path),
);
expect(
() => response.unwrap<Object?>(),
throwsA(
isA<CoreMethodException>().having((e) => e.code, 'code', 'spill_error'),
),
);
expect(file.existsSync(), isTrue);
});
test('a missing file fails the call instead of the transport', () async {
final response = await resolveSpilledResponse(
CoreMethodResponse(id: '4', file: join(spillDirectory.path, 'gone.json')),
);
expect(response.error?.code, 'spill_error');
});
test('reads a file the Core built there as text and deletes it', () async {
final built = File(join(spillDirectory.path, 'built.yaml'))
..writeAsStringSync('mode: rule\n');
expect(await readSpilledText(built.path), 'mode: rule\n');
expect(built.existsSync(), isFalse);
});
test('refuses to read text outside the spill directory', () async {
final outside = File(join(home.path, 'built.yaml'))
..writeAsStringSync('secret');
await expectLater(
readSpilledText(outside.path),
throwsA(
isA<CoreMethodException>().having((e) => e.code, 'code', 'spill_error'),
),
);
expect(outside.existsSync(), isTrue);
});
test('discarding deletes only a spilled file', () async {
final spilled = spill(spillDirectory, {'id': '5', 'result': null});
final outside = spill(home, {'id': '6', 'result': null});
await discardSpilledResponse(CoreMethodResponse(file: spilled.path));
await discardSpilledResponse(CoreMethodResponse(file: outside.path));
expect(spilled.existsSync(), isFalse);
expect(outside.existsSync(), isTrue);
});
test('discarding a late built profile deletes the file it names', () async {
final built = File(join(spillDirectory.path, 'built.yaml'))
..writeAsStringSync('proxies: []');
await discardSpilledResponse(
CoreMethodResponse(id: '7', result: built.path),
);
await discardSpilledResponse(
const CoreMethodResponse(id: '8', result: 'not a path'),
);
expect(built.existsSync(), isFalse);
});
}