feat: move native communication to FFI (#1457)

This commit is contained in:
Levi authored and GitHub committed 2026-09-05 20:18:42 +08:00
1 parent 177c225f16
commit 39cd9034cd
86 files changed
+4558 -886

No files matched your search

@@ -0,0 +1,41 @@
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gopeed/api/api.dart' as api;
import 'package:gopeed/core/network/gopeed/gopeed_transport.dart';
import 'package:gopeed/features/auth/application/web_auth_controller.dart';
void main() {
test('successful login relies on the server cookie and unauthorized requires login', () async {
api.setTransportForTesting(_LoginTransport());
final controller = WebAuthController();
addTearDown(controller.dispose);
final failure = await controller.login(username: 'gopeed', password: 'secret');
expect(failure, isNull);
controller.requireLogin();
expect(controller.loginRequired, isTrue);
});
}
class _LoginTransport implements GopeedTransport {
@override
Future<dynamic> request(
String path, {
String method = 'GET',
dynamic data,
Map<String, dynamic>? queryParameters,
}) async {
expect(path, 'api/web/login');
expect(method, 'POST');
return {'code': 0};
}
@override
Future<Response<String>> proxyRequest(String uri, {dynamic data, Options? options}) {
throw UnimplementedError();
}
@override
String join(String path) => path;
}
@@ -6,6 +6,7 @@ import 'package:gopeed/core/capabilities/app_capabilities.dart';
import 'package:gopeed/core/capabilities/capability_rpc.dart';
import 'package:gopeed/core/capabilities/gopeed_capability.dart';
import 'package:gopeed/core/common/start_config.dart';
import 'package:gopeed/core/common/api_server_state.dart';
import 'package:gopeed/features/settings/application/settings_controller.dart';
void main() {
@@ -36,13 +37,51 @@ void main() {
expect((await container.read(settingsControllerProvider.future)).config.downloadDir, 'E:/Created task');
expect(getConfigCalls, 2);
});
test('settings save preserves backend-owned client preferences', () async {
final backend = DownloaderConfig()
..extra.windowState = WindowStateConfig(isMaximized: true, width: 1200, height: 800)
..extra.createHistory = ['https://example.com/existing.zip']
..extra.analyticsClientId = 'existing-client';
DownloaderConfig? saved;
final registry = CapabilityRegistry(createAppCapabilityCodecs())
..bind(GopeedMethods.getConfig, (_) => DownloaderConfig.fromJson(backend.toJson()))
..bind(GopeedMethods.putConfig, (config) {
saved = DownloaderConfig.fromJson(config.toJson());
return const RpcUnit();
});
final container = ProviderContainer(
overrides: [
appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry))),
appRuntimeControllerProvider.overrideWith(_TestRuntimeController.new),
],
);
addTearDown(container.dispose);
final controller = container.read(settingsControllerProvider.notifier);
await container.read(settingsControllerProvider.future);
await controller.save(DownloaderConfig(downloadDir: 'E:/New'));
expect(saved?.downloadDir, 'E:/New');
expect(saved?.extra.windowState.isMaximized, isTrue);
expect(saved?.extra.createHistory, ['https://example.com/existing.zip']);
expect(saved?.extra.analyticsClientId, 'existing-client');
});
}
class _TestRuntimeController extends AppRuntimeController {
@override
Future<AppRuntimeState> build() async => AppRuntimeState(
startConfig: StartConfig(),
runningPort: 0,
apiServerState: const ApiServerState(
enabled: false,
running: false,
network: '',
address: '',
runningPort: 0,
pendingApply: false,
lastError: '',
),
downloaderConfig: DownloaderConfig(downloadDir: 'C:/Stale runtime snapshot'),
);
}
@@ -145,21 +145,30 @@ void main() {
final rememberDirectory = find.byKey(const ValueKey('create-task-remember-download-directory-checkbox'));
final directDownloadCheckbox = find.byKey(const ValueKey('create-task-direct-download-checkbox'));
final rememberDirectoryRow = find.byKey(const ValueKey('create-task-remember-download-directory'));
final rememberDirectoryLabel = find.byKey(const ValueKey('create-task-remember-download-directory-label'));
final directDownloadLabel = find.descendant(
of: find.byKey(const ValueKey('create-task-direct-download-toggle')),
matching: find.text('Skip resolving and create tasks immediately'),
);
final directoryComponent = find.byKey(const ValueKey('create-task-directory-component'));
final directoryOptionsRow = find.byKey(const ValueKey('create-task-directory-options-row'));
final directoryOptionsLayout = find.byKey(const ValueKey('create-task-directory-options-layout'));
final directoryPicker = find.byKey(const ValueKey('create-task-directory-picker'));
expect(tester.widget<shad.Checkbox>(rememberDirectory).state, shad.CheckboxState.unchecked);
expect(find.text('Save as default download directory'), findsOneWidget);
final rememberStyle = tester.widget<Text>(rememberDirectoryLabel).style!;
final directStyle = tester.widget<Text>(directDownloadLabel).style!;
expect(rememberStyle.color, directStyle.color);
expect(rememberStyle.fontSize, directStyle.fontSize);
expect(rememberStyle.fontWeight, directStyle.fontWeight);
expect(tester.widget<shad.Checkbox>(rememberDirectory).size, isNull);
expect(tester.getSize(rememberDirectory), const Size.square(AppDesignTokens.checkboxSize));
expect(tester.getCenter(rememberDirectory).dx, closeTo(tester.getCenter(directDownloadCheckbox).dx, 0.5));
final rememberDirectoryLabel = tester.widget<Text>(
find.byKey(const ValueKey('create-task-remember-download-directory-label')),
);
final directDownloadLabel = tester.widget<Text>(find.byKey(const ValueKey('create-task-direct-download-label')));
expect(rememberDirectoryLabel.style, directDownloadLabel.style);
expect(rememberDirectoryLabel.maxLines, directDownloadLabel.maxLines);
expect(rememberDirectoryLabel.overflow, directDownloadLabel.overflow);
final rememberDirectoryText = tester.widget<Text>(rememberDirectoryLabel);
final directDownloadText = tester.widget<Text>(directDownloadLabel);
expect(rememberDirectoryText.style, directDownloadText.style);
expect(rememberDirectoryText.maxLines, directDownloadText.maxLines);
expect(rememberDirectoryText.overflow, directDownloadText.overflow);
expect(tester.widget<shad.IconButton>(directoryPicker).variance, same(shad.ButtonVariance.outline));
expect(tester.widget<flutter.Row>(directoryOptionsLayout).mainAxisAlignment, MainAxisAlignment.spaceBetween);
expect(tester.getCenter(rememberDirectoryRow).dy, closeTo(tester.getCenter(shortcutRow).dy, 0.5));