Add proxy routing and sing-box support

This commit is contained in:
Fabian Freund
2026-05-22 18:16:31 +02:00
parent 51289f1266
commit a5974617aa
262 changed files with 32003 additions and 3962 deletions
@@ -24,7 +24,7 @@ import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/experimental/persist.dart';
import 'package:riverpod/riverpod.dart';
import 'package:search_backend/search_backend.dart';
import 'package:search_protocol/search_protocol.dart';
import 'package:weblibre/features/search_credits/domain/controllers/search_token_issuance_controller.dart';
import 'package:weblibre/features/user/data/providers.dart';
import 'package:weblibre/features/web_search/domain/controllers/search_controller.dart';
@@ -125,23 +125,25 @@ void main() {
expect(decoded['url'], contains('t='));
});
test('err=1 starts the loader in the error pane with a retry button',
() async {
final url = await server.loaderUrl(
tabId: 'tab-9',
captureId: 'failed9',
error: true,
);
final res = await _get(url);
expect(res.statusCode, 200);
// Retry is now a JS-driven button, not an HTML form — assert the
// shell starts in error mode (error pane visible, pending hidden)
// and that the button + JS retry endpoint string are present.
expect(res.body, contains('id="retry"'));
expect(res.body, contains('id="error" class="error"'));
expect(res.body, contains('id="pending" class="hidden"'));
expect(res.body, contains('/loader/retry?tab='));
});
test(
'err=1 starts the loader in the error pane with a retry button',
() async {
final url = await server.loaderUrl(
tabId: 'tab-9',
captureId: 'failed9',
error: true,
);
final res = await _get(url);
expect(res.statusCode, 200);
// Retry is now a JS-driven button, not an HTML form — assert the
// shell starts in error mode (error pane visible, pending hidden)
// and that the button + JS retry endpoint string are present.
expect(res.body, contains('id="retry"'));
expect(res.body, contains('id="error" class="error"'));
expect(res.body, contains('id="pending" class="hidden"'));
expect(res.body, contains('/loader/retry?tab='));
},
);
test('loader with invalid capture id returns 404', () async {
final port = await server.ensureStarted();
@@ -174,9 +176,7 @@ void main() {
// Start the long-poll while nothing has been published yet — it
// should block on the per-id completer, not busy-poll the filesystem.
final pollFuture = _get(
Uri.parse(
'http://127.0.0.1:$port/loader/wait?tab=t&capture=signaled',
),
Uri.parse('http://127.0.0.1:$port/loader/wait?tab=t&capture=signaled'),
);
// Race-free: publish only after the request hit the handler. A short
// microtask flush gives the handler time to register its waiter.
@@ -192,23 +192,25 @@ void main() {
});
group('retry route', () {
test('emits a RetryRequest on retryRequests stream and returns 204',
() async {
final port = await server.ensureStarted();
final retryFuture = server.retryRequests.first;
test(
'emits a RetryRequest on retryRequests stream and returns 204',
() async {
final port = await server.ensureStarted();
final retryFuture = server.retryRequests.first;
final res = await _post(
Uri.parse(
'http://127.0.0.1:$port/loader/retry?tab=tab-42&capture=cap42',
),
);
// 204 No Content — the JS loader re-polls; a body would be wasted.
expect(res.statusCode, 204);
final res = await _post(
Uri.parse(
'http://127.0.0.1:$port/loader/retry?tab=tab-42&capture=cap42',
),
);
// 204 No Content — the JS loader re-polls; a body would be wasted.
expect(res.statusCode, 204);
final req = await retryFuture.timeout(const Duration(seconds: 2));
expect(req.tabId, 'tab-42');
expect(req.captureId, 'cap42');
});
final req = await retryFuture.timeout(const Duration(seconds: 2));
expect(req.tabId, 'tab-42');
expect(req.captureId, 'cap42');
},
);
test('retry rejects GET with 405', () async {
final port = await server.ensureStarted();
@@ -20,7 +20,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:search_backend/search_backend.dart';
import 'package:search_protocol/search_protocol.dart';
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
import 'package:weblibre/features/user/domain/providers.dart';
@@ -79,14 +79,14 @@ void main() {
),
),
);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 100));
expect(find.text('Heading'), findsOneWidget);
expect(find.text('Body copy'), findsOneWidget);
expect(find.text('Fabian'), findsOneWidget);
await tester.tap(find.byTooltip('Open in browser'));
await tester.pumpAndSettle();
await tester.pump();
expect(opener.openedUris, [Uri.parse(url)]);
});
@@ -23,7 +23,9 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:search_backend/search_backend.dart';
import 'package:search_protocol/search_protocol.dart';
import 'package:weblibre/features/search_credits/data/models/web_search_settings.dart';
import 'package:weblibre/features/search_credits/domain/repositories/web_search_settings.dart';
import 'package:weblibre/features/user/domain/providers.dart';
import 'package:weblibre/features/web_search/domain/controllers/search_controller.dart';
import 'package:weblibre/features/web_search/presentation/widgets/search_result_card.dart';
@@ -39,12 +41,15 @@ void main() {
ProviderScope(
overrides: [
...webSearchTestOverrides(),
metaSearchControllerProvider.overrideWithValue(
MetaSearchState(status: WebSearchStatus.ready),
),
watchCachedIconBytesProvider.overrideWith((ref, origin) {
return Stream.value(null);
}),
metaSearchControllerProvider.overrideWithValue(
const MetaSearchState(status: WebSearchStatus.ready),
),
webSearchSettingsControllerProvider.overrideWithValue(
WebSearchSettings.withDefaults(),
),
],
child: MaterialApp(
home: Scaffold(
@@ -67,7 +72,7 @@ void main() {
);
await tester.tap(find.text('LensAI result'));
await tester.pumpAndSettle();
await tester.pump();
expect(openedUris, [Uri.parse(url)]);
});
@@ -84,15 +89,18 @@ void main() {
ProviderScope(
overrides: [
...webSearchTestOverrides(),
watchCachedIconBytesProvider.overrideWith((ref, origin) {
return Stream.value(null);
}),
metaSearchControllerProvider.overrideWithValue(
MetaSearchState(
status: WebSearchStatus.ready,
imagesByUrl: {thumbnailUrl: Uint8List.fromList(pngBytes)},
),
),
watchCachedIconBytesProvider.overrideWith((ref, origin) {
return Stream.value(null);
}),
webSearchSettingsControllerProvider.overrideWithValue(
WebSearchSettings.withDefaults(),
),
],
child: MaterialApp(
home: Scaffold(
@@ -114,7 +122,7 @@ void main() {
),
);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 100));
expect(find.byType(Image), findsOneWidget);
},